<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Interactive BOM for KiCAD</title>
  <style type="text/css">
:root {
  --pcb-edge-color: black;
  --pad-color: #878787;
  --pad-hole-color: #CCCCCC;
  --pad-color-highlight: #D04040;
  --pin1-outline-color: #ffb629;
  --pin1-outline-color-highlight: #b4ff03;
  --silkscreen-edge-color: #aa4;
  --silkscreen-polygon-color: #4aa;
  --silkscreen-text-color: #4aa;
  --fabrication-edge-color: #907651;
  --fabrication-polygon-color: #907651;
  --fabrication-text-color: #a27c24;
  --track-color: #def5f1;
  --track-color-highlight: #D04040;
  --zone-color: #def5f1;
  --zone-color-highlight: #d0404080;
}

html, body {
  margin: 0px;
  height: 100%;
  font-family: Verdana, sans-serif;
}

.dark.topmostdiv {
  --pcb-edge-color: #eee;
  --pad-color: #808080;
  --pin1-outline-color: #ffa800;
  --pin1-outline-color-highlight: #ccff00;
  --track-color: #42524f;
  --zone-color: #42524f;
  background-color: #252c30;
  color: #eee;
}

button {
  background-color: #eee;
  border: 1px solid #888;
  color: black;
  height: 44px;
  width: 44px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 14px;
  font-weight: bolder;
}

.dark button {
  /* This will be inverted */
  background-color: #c3b7b5;
}

button.depressed {
  background-color: #0a0;
  color: white;
}

.dark button.depressed {
  /* This will be inverted */
  background-color: #b3b;
}

button:focus {
  outline: 0;
}

button#tb-btn {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8.47 8.47'%3E%3Crect transform='translate(0 -288.53)' ry='1.17' y='288.8' x='.27' height='7.94' width='7.94' fill='%23f9f9f9'/%3E%3Cg transform='translate(0 -288.53)'%3E%3Crect width='7.94' height='7.94' x='.27' y='288.8' ry='1.17' fill='none' stroke='%23000' stroke-width='.4' stroke-linejoin='round'/%3E%3Cpath d='M1.32 290.12h5.82M1.32 291.45h5.82' fill='none' stroke='%23000' stroke-width='.4'/%3E%3Cpath d='M4.37 292.5v4.23M.26 292.63H8.2' fill='none' stroke='%23000' stroke-width='.3'/%3E%3Ctext font-weight='700' font-size='3.17' font-family='sans-serif'%3E%3Ctspan x='1.35' y='295.73'%3EF%3C/tspan%3E%3Ctspan x='5.03' y='295.68'%3EB%3C/tspan%3E%3C/text%3E%3C/g%3E%3C/svg%3E%0A");
}

button#lr-btn {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8.47 8.47'%3E%3Crect transform='translate(0 -288.53)' ry='1.17' y='288.8' x='.27' height='7.94' width='7.94' fill='%23f9f9f9'/%3E%3Cg transform='translate(0 -288.53)'%3E%3Crect width='7.94' height='7.94' x='.27' y='288.8' ry='1.17' fill='none' stroke='%23000' stroke-width='.4' stroke-linejoin='round'/%3E%3Cpath d='M1.06 290.12H3.7m-2.64 1.33H3.7m-2.64 1.32H3.7m-2.64 1.3H3.7m-2.64 1.33H3.7' fill='none' stroke='%23000' stroke-width='.4'/%3E%3Cpath d='M4.37 288.8v7.94m0-4.11h3.96' fill='none' stroke='%23000' stroke-width='.3'/%3E%3Ctext font-weight='700' font-size='3.17' font-family='sans-serif'%3E%3Ctspan x='5.11' y='291.96'%3EF%3C/tspan%3E%3Ctspan x='5.03' y='295.68'%3EB%3C/tspan%3E%3C/text%3E%3C/g%3E%3C/svg%3E%0A");
}

button#bom-btn {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8.47 8.47'%3E%3Crect transform='translate(0 -288.53)' ry='1.17' y='288.8' x='.27' height='7.94' width='7.94' fill='%23f9f9f9'/%3E%3Cg transform='translate(0 -288.53)' fill='none' stroke='%23000' stroke-width='.4'%3E%3Crect width='7.94' height='7.94' x='.27' y='288.8' ry='1.17' stroke-linejoin='round'/%3E%3Cpath d='M1.59 290.12h5.29M1.59 291.45h5.33M1.59 292.75h5.33M1.59 294.09h5.33M1.59 295.41h5.33'/%3E%3C/g%3E%3C/svg%3E");
}

button#bom-grouped-btn {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cg stroke='%23000' stroke-linejoin='round' class='layer'%3E%3Crect width='29' height='29' x='1.5' y='1.5' stroke-width='2' fill='%23fff' rx='5' ry='5'/%3E%3Cpath stroke-linecap='square' stroke-width='2' d='M6 10h4m4 0h5m4 0h3M6.1 22h3m3.9 0h5m4 0h4m-16-8h4m4 0h4'/%3E%3Cpath stroke-linecap='null' d='M5 17.5h22M5 26.6h22M5 5.5h22'/%3E%3C/g%3E%3C/svg%3E");
}

button#bom-ungrouped-btn {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cg stroke='%23000' stroke-linejoin='round' class='layer'%3E%3Crect width='29' height='29' x='1.5' y='1.5' stroke-width='2' fill='%23fff' rx='5' ry='5'/%3E%3Cpath stroke-linecap='square' stroke-width='2' d='M6 10h4m-4 8h3m-3 8h4'/%3E%3Cpath stroke-linecap='null' d='M5 13.5h22m-22 8h22M5 5.5h22'/%3E%3C/g%3E%3C/svg%3E");
}

button#bom-netlist-btn {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cg fill='none' stroke='%23000' class='layer'%3E%3Crect width='29' height='29' x='1.5' y='1.5' stroke-width='2' fill='%23fff' rx='5' ry='5'/%3E%3Cpath stroke-width='2' d='M6 26l6-6v-8m13.8-6.3l-6 6v8'/%3E%3Ccircle cx='11.8' cy='9.5' r='2.8' stroke-width='2'/%3E%3Ccircle cx='19.8' cy='22.8' r='2.8' stroke-width='2'/%3E%3C/g%3E%3C/svg%3E");
}

button#copy {
  background-image: url("data:image/svg+xml,%3Csvg height='48' viewBox='0 0 48 48' width='48' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0h48v48h-48z' fill='none'/%3E%3Cpath d='M32 2h-24c-2.21 0-4 1.79-4 4v28h4v-28h24v-4zm6 8h-22c-2.21 0-4 1.79-4 4v28c0 2.21 1.79 4 4 4h22c2.21 0 4-1.79 4-4v-28c0-2.21-1.79-4-4-4zm0 32h-22v-28h22v28z'/%3E%3C/svg%3E");
  background-position: 6px 6px;
  background-repeat: no-repeat;
  background-size: 26px 26px;
  border-radius: 6px;
  height: 40px;
  width: 40px;
  margin: 10px 5px;
}

button#copy:active {
    box-shadow: inset 0px 0px 5px #6c6c6c;
}

textarea.clipboard-temp {
  position: fixed;
  top: 0;
  left: 0;
  width: 2em;
  height: 2em;
  padding: 0;
  border: None;
  outline: None;
  box-shadow: None;
  background: transparent;
}

.left-most-button {
  border-right: 0;
  border-top-left-radius: 6px;
  border-bottom-left-radius: 6px;
}

.middle-button {
  border-right: 0;
}

.right-most-button {
  border-top-right-radius: 6px;
  border-bottom-right-radius: 6px;
}

.button-container {
  font-size: 0;
  margin: 10px 10px 10px 0px;
}

.dark .button-container {
  filter: invert(1);
}

.button-container button {
  background-size: 32px 32px;
  background-position: 5px 5px;
  background-repeat: no-repeat;
}

@media print {
  .hideonprint {
    display: none;
  }
}

canvas {
  cursor: crosshair;
}

canvas:active {
  cursor: grabbing;
}

.fileinfo {
  width: 100%;
  max-width: 1000px;
  border: none;
  padding: 5px;
}

.fileinfo .title {
  font-size: 20pt;
  font-weight: bold;
}

.fileinfo td {
  overflow: hidden;
  white-space: nowrap;
  max-width: 1px;
  width: 50%;
  text-overflow: ellipsis;
}

.bom {
  border-collapse: collapse;
  font-family: Consolas, "DejaVu Sans Mono", Monaco, monospace;
  font-size: 10pt;
  table-layout: fixed;
  width: 100%;
  margin-top: 1px;
}

.bom th, .bom td {
  border: 1px solid black;
  padding: 5px;
  word-wrap: break-word;
  text-align: center;
  position: relative;
}

.dark .bom th, .dark .bom td {
  border: 1px solid #777;
}

.bom th {
  background-color: #CCCCCC;
  background-clip: padding-box;
}

.dark .bom th {
  background-color: #3b4749;
}

.bom tr.highlighted:nth-child(n) {
  background-color: #cfc;
}

.dark .bom tr.highlighted:nth-child(n) {
  background-color: #226022;
}

.bom tr:nth-child(even) {
  background-color: #f2f2f2;
}

.dark .bom tr:nth-child(even) {
  background-color: #313b40;
}

.bom tr.checked {
  color: #aaa;
}

.dark .bom tr.checked {
  color: #666;
}

.bom tr {
  transition: background-color 0.2s;
}

.bom .numCol {
  width: 25px;
}

.bom .Description {
  width: 10%;
}

.bom .Part {
  width: 10%;
}

.bom .Value {
  width: 15%;
}

.bom .Quantity {
  width: 65px;
}

.bom th .sortmark {
  position: absolute;
  right: 1px;
  top: 1px;
  margin-top: -5px;
  border-width: 5px;
  border-style: solid;
  border-color: transparent transparent #221 transparent;
  transform-origin: 50% 85%;
  transition: opacity 0.2s, transform 0.4s;
}

.dark .bom th .sortmark {
  filter: invert(1);
}

.bom th .sortmark.none {
  opacity: 0;
}

.bom th .sortmark.desc {
  transform: rotate(180deg);
}

.bom th:hover .sortmark.none {
  opacity: 0.5;
}

.bom .bom-checkbox {
  width: 30px;
  position: relative;
  user-select: none;
  -moz-user-select: none;
}

.bom .bom-checkbox:before {
  content: "";
  position: absolute;
  border-width: 15px;
  border-style: solid;
  border-color: #51829f transparent transparent transparent;
  visibility: hidden;
  top: -15px;
}

.bom .bom-checkbox:after {
  content: "Double click to set/unset all";
  position: absolute;
  color: white;
  top: -35px;
  left: -26px;
  background: #51829f;
  padding: 5px 15px;
  border-radius: 8px;
  white-space: nowrap;
  visibility: hidden;
}

.bom .bom-checkbox:hover:before, .bom .bom-checkbox:hover:after {
  visibility: visible;
  transition: visibility 0.2s linear 1s;
}

.split {
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
  overflow-y: auto;
  overflow-x: hidden;
  background-color: inherit;
}

.split.split-horizontal, .gutter.gutter-horizontal {
  height: 100%;
  float: left;
}

.gutter {
  background-color: #ddd;
  background-repeat: no-repeat;
  background-position: 50%;
  transition: background-color 0.3s;
}

.dark .gutter {
  background-color: #777;
}

.gutter.gutter-horizontal {
  background-image: url('');
  cursor: ew-resize;
  width: 5px;
}

.gutter.gutter-vertical {
  background-image: url('');
  cursor: ns-resize;
  height: 5px;
}

.searchbox {
  float: left;
  height: 40px;
  margin: 10px 5px;
  padding: 12px 32px;
  font-family: Consolas, "DejaVu Sans Mono", Monaco, monospace;
  font-size: 18px;
  box-sizing: border-box;
  border: 1px solid #888;
  border-radius: 6px;
  outline: none;
  background-color: #eee;
  transition: background-color 0.2s, border 0.2s;
  background-image: url('');
  background-position: 10px 10px;
  background-repeat: no-repeat;
}

.dark .searchbox {
  background-color: #111;
  color: #eee;
}

.searchbox::placeholder {
  color: #ccc;
}

.dark .searchbox::placeholder {
  color: #666;
}

.filter {
  width: calc(60% - 64px);
}

.reflookup {
  width: calc(40% - 10px);
}

input[type=text]:focus {
  background-color: white;
  border: 1px solid #333;
}

.dark input[type=text]:focus {
  background-color: #333;
  border: 1px solid #ccc;
}

mark.highlight {
  background-color: #5050ff;
  color: #fff;
  padding: 2px;
  border-radius: 6px;
}

.dark mark.highlight {
  background-color: #76a6da;
  color: #111;
}

.menubtn {
  background-color: white;
  border: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='36' viewBox='0 0 20 20'%3E%3Cpath fill='none' d='M0 0h20v20H0V0z'/%3E%3Cpath d='M15.95 10.78c.03-.25.05-.51.05-.78s-.02-.53-.06-.78l1.69-1.32c.15-.12.19-.34.1-.51l-1.6-2.77c-.1-.18-.31-.24-.49-.18l-1.99.8c-.42-.32-.86-.58-1.35-.78L12 2.34c-.03-.2-.2-.34-.4-.34H8.4c-.2 0-.36.14-.39.34l-.3 2.12c-.49.2-.94.47-1.35.78l-1.99-.8c-.18-.07-.39 0-.49.18l-1.6 2.77c-.1.18-.06.39.1.51l1.69 1.32c-.04.25-.07.52-.07.78s.02.53.06.78L2.37 12.1c-.15.12-.19.34-.1.51l1.6 2.77c.1.18.31.24.49.18l1.99-.8c.42.32.86.58 1.35.78l.3 2.12c.04.2.2.34.4.34h3.2c.2 0 .37-.14.39-.34l.3-2.12c.49-.2.94-.47 1.35-.78l1.99.8c.18.07.39 0 .49-.18l1.6-2.77c.1-.18.06-.39-.1-.51l-1.67-1.32zM10 13c-1.65 0-3-1.35-3-3s1.35-3 3-3 3 1.35 3 3-1.35 3-3 3z'/%3E%3C/svg%3E%0A");
  background-position: center;
  background-repeat: no-repeat;
}

.statsbtn {
  background-color: white;
  border: none;
  background-image: url("data:image/svg+xml,%3Csvg width='36' height='36' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 6h28v24H4V6zm0 8h28v8H4m9-16v24h10V5.8' fill='none' stroke='%23000' stroke-width='2'/%3E%3C/svg%3E");
  background-position: center;
  background-repeat: no-repeat;
}

.iobtn {
  background-color: white;
  border: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='36'%3E%3Cpath fill='none' stroke='%23000' stroke-width='2' d='M3 33v-7l6.8-7h16.5l6.7 7v7H3zM3.2 26H33M21 9l5-5.9 5 6h-2.5V15h-5V9H21zm-4.9 0l-5 6-5-6h2.5V3h5v6h2.5z'/%3E%3Cpath fill='none' stroke='%23000' d='M6.1 29.5H10'/%3E%3C/svg%3E");
  background-position: center;
  background-repeat: no-repeat;
}

.dark .statsbtn, .dark .savebtn, .dark .menubtn, .dark .iobtn {
  filter: invert(1);
}

.flexbox {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
}

.savebtn {
  background-color: #d6d6d6;
  width: auto;
  height: 30px;
  flex-grow: 1;
  margin: 5px;
  border-radius: 4px;
}

.savebtn:active {
  background-color: #0a0;
  color: white;
}

.dark .savebtn:active {
  /* This will be inverted */
  background-color: #b3b;
}

.stats {
  border-collapse: collapse;
  font-size: 12pt;
  table-layout: fixed;
  width: 100%;
  min-width: 450px;
}

.dark .stats td {
  border: 1px solid #bbb;
}

.stats td {
  border: 1px solid black;
  padding: 5px;
  word-wrap: break-word;
  text-align: center;
  position: relative;
}

#checkbox-stats div {
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

#checkbox-stats .bar {
  background-color: rgba(28, 251, 0, 0.6);
}

.menu {
  position: relative;
  display: inline-block;
  margin: 10px 10px 10px 0px;
}

.menu-content {
  display: none;
  position: absolute;
  background-color: white;
  right: 0;
  min-width: 300px;
  box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
  z-index: 100;
  padding: 8px;
}

.dark .menu-content {
  background-color: #111;
}

.menu:hover .menu-content {
  display: block;
}

.menu:hover .menubtn, .menu:hover .iobtn, .menu:hover .statsbtn {
  background-color: #eee;
}

.menu-label {
  display: inline-block;
  padding: 8px;
  border: 1px solid #ccc;
  border-top: 0;
  width: calc(100% - 18px);
}

.menu-label-top {
  border-top: 1px solid #ccc;
}

.menu-textbox {
  float: left;
  height: 24px;
  margin: 10px 5px;
  padding: 5px 5px;
  font-family: Consolas, "DejaVu Sans Mono", Monaco, monospace;
  font-size: 14px;
  box-sizing: border-box;
  border: 1px solid #888;
  border-radius: 4px;
  outline: none;
  background-color: #eee;
  transition: background-color 0.2s, border 0.2s;
  width: calc(100% - 10px);
}

.menu-textbox.invalid, .dark .menu-textbox.invalid {
  color: red;
}

.dark .menu-textbox {
  background-color: #222;
  color: #eee;
}

.radio-container {
  margin: 4px;
}

.topmostdiv {
  width: 100%;
  height: 100%;
  background-color: white;
  transition: background-color 0.3s;
}

#top {
  height: 78px;
  border-bottom: 2px solid black;
}

.dark #top {
  border-bottom: 2px solid #ccc;
}

#dbg {
  display: block;
}

::-webkit-scrollbar {
  width: 8px;
}

::-webkit-scrollbar-track {
  background: #aaa;
}

::-webkit-scrollbar-thumb {
  background: #666;
  border-radius: 3px;
}

::-webkit-scrollbar-thumb:hover {
  background: #555;
}

.slider {
  -webkit-appearance: none;
  width: 100%;
  margin: 3px 0;
  padding: 0;
  outline: none;
  opacity: 0.7;
  -webkit-transition: .2s;
  transition: opacity .2s;
  border-radius: 3px;
}

.slider:hover {
  opacity: 1;
}

.slider:focus {
  outline: none;
}

.slider::-webkit-slider-runnable-track {
  -webkit-appearance: none;
  width: 100%;
  height: 8px;
  background: #d3d3d3;
  border-radius: 3px;
  border: none;
}

.slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: #0a0;
  cursor: pointer;
  margin-top: -4px;
}

.dark .slider::-webkit-slider-thumb {
  background: #3d3;
}

.slider::-moz-range-thumb {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: #0a0;
  cursor: pointer;
}

.slider::-moz-range-track {
  height: 8px;
  background: #d3d3d3;
  border-radius: 3px;
}

.dark .slider::-moz-range-thumb {
  background: #3d3;
}

.slider::-ms-track {
  width: 100%;
  height: 8px;
  border-width: 3px 0;
  background: transparent;
  border-color: transparent;
  color: transparent;
  transition: opacity .2s;
}

.slider::-ms-fill-lower {
  background: #d3d3d3;
  border: none;
  border-radius: 3px;
}

.slider::-ms-fill-upper {
  background: #d3d3d3;
  border: none;
  border-radius: 3px;
}

.slider::-ms-thumb {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: #0a0;
  cursor: pointer;
  margin: 0;
}

.shameless-plug {
  font-size: 0.8em;
  text-align: center;
  display: block;
}

a {
  color: #0278a4;
}

.dark a {
  color: #00b9fd;
}

#frontcanvas, #backcanvas {
    touch-action: none;
}

 
  </style>
  <script type="text/javascript" >
///////////////////////////////////////////////
/*
  Split.js - v1.3.5
  MIT License
  https://github.com/nathancahill/Split.js
*/
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Split=t()}(this,function(){"use strict";var e=window,t=e.document,n="addEventListener",i="removeEventListener",r="getBoundingClientRect",s=function(){return!1},o=e.attachEvent&&!e[n],a=["","-webkit-","-moz-","-o-"].filter(function(e){var n=t.createElement("div");return n.style.cssText="width:"+e+"calc(9px)",!!n.style.length}).shift()+"calc",l=function(e){return"string"==typeof e||e instanceof String?t.querySelector(e):e};return function(u,c){function z(e,t,n){var i=A(y,t,n);Object.keys(i).forEach(function(t){return e.style[t]=i[t]})}function h(e,t){var n=B(y,t);Object.keys(n).forEach(function(t){return e.style[t]=n[t]})}function f(e){var t=E[this.a],n=E[this.b],i=t.size+n.size;t.size=e/this.size*i,n.size=i-e/this.size*i,z(t.element,t.size,this.aGutterSize),z(n.element,n.size,this.bGutterSize)}function m(e){var t;this.dragging&&((t="touches"in e?e.touches[0][b]-this.start:e[b]-this.start)<=E[this.a].minSize+M+this.aGutterSize?t=E[this.a].minSize+this.aGutterSize:t>=this.size-(E[this.b].minSize+M+this.bGutterSize)&&(t=this.size-(E[this.b].minSize+this.bGutterSize)),f.call(this,t),c.onDrag&&c.onDrag())}function g(){var e=E[this.a].element,t=E[this.b].element;this.size=e[r]()[y]+t[r]()[y]+this.aGutterSize+this.bGutterSize,this.start=e[r]()[G]}function d(){var t=this,n=E[t.a].element,r=E[t.b].element;t.dragging&&c.onDragEnd&&c.onDragEnd(),t.dragging=!1,e[i]("mouseup",t.stop),e[i]("touchend",t.stop),e[i]("touchcancel",t.stop),t.parent[i]("mousemove",t.move),t.parent[i]("touchmove",t.move),delete t.stop,delete t.move,n[i]("selectstart",s),n[i]("dragstart",s),r[i]("selectstart",s),r[i]("dragstart",s),n.style.userSelect="",n.style.webkitUserSelect="",n.style.MozUserSelect="",n.style.pointerEvents="",r.style.userSelect="",r.style.webkitUserSelect="",r.style.MozUserSelect="",r.style.pointerEvents="",t.gutter.style.cursor="",t.parent.style.cursor=""}function S(t){var i=this,r=E[i.a].element,o=E[i.b].element;!i.dragging&&c.onDragStart&&c.onDragStart(),t.preventDefault(),i.dragging=!0,i.move=m.bind(i),i.stop=d.bind(i),e[n]("mouseup",i.stop),e[n]("touchend",i.stop),e[n]("touchcancel",i.stop),i.parent[n]("mousemove",i.move),i.parent[n]("touchmove",i.move),r[n]("selectstart",s),r[n]("dragstart",s),o[n]("selectstart",s),o[n]("dragstart",s),r.style.userSelect="none",r.style.webkitUserSelect="none",r.style.MozUserSelect="none",r.style.pointerEvents="none",o.style.userSelect="none",o.style.webkitUserSelect="none",o.style.MozUserSelect="none",o.style.pointerEvents="none",i.gutter.style.cursor=j,i.parent.style.cursor=j,g.call(i)}function v(e){e.forEach(function(t,n){if(n>0){var i=F[n-1],r=E[i.a],s=E[i.b];r.size=e[n-1],s.size=t,z(r.element,r.size,i.aGutterSize),z(s.element,s.size,i.bGutterSize)}})}function p(){F.forEach(function(e){e.parent.removeChild(e.gutter),E[e.a].element.style[y]="",E[e.b].element.style[y]=""})}void 0===c&&(c={});var y,b,G,E,w=l(u[0]).parentNode,D=e.getComputedStyle(w).flexDirection,U=c.sizes||u.map(function(){return 100/u.length}),k=void 0!==c.minSize?c.minSize:100,x=Array.isArray(k)?k:u.map(function(){return k}),L=void 0!==c.gutterSize?c.gutterSize:10,M=void 0!==c.snapOffset?c.snapOffset:30,O=c.direction||"horizontal",j=c.cursor||("horizontal"===O?"ew-resize":"ns-resize"),C=c.gutter||function(e,n){var i=t.createElement("div");return i.className="gutter gutter-"+n,i},A=c.elementStyle||function(e,t,n){var i={};return"string"==typeof t||t instanceof String?i[e]=t:i[e]=o?t+"%":a+"("+t+"% - "+n+"px)",i},B=c.gutterStyle||function(e,t){return n={},n[e]=t+"px",n;var n};"horizontal"===O?(y="width","clientWidth",b="clientX",G="left","paddingLeft"):"vertical"===O&&(y="height","clientHeight",b="clientY",G="top","paddingTop");var F=[];return E=u.map(function(e,t){var i,s={element:l(e),size:U[t],minSize:x[t]};if(t>0&&(i={a:t-1,b:t,dragging:!1,isFirst:1===t,isLast:t===u.length-1,direction:O,parent:w},i.aGutterSize=L,i.bGutterSize=L,i.isFirst&&(i.aGutterSize=L/2),i.isLast&&(i.bGutterSize=L/2),"row-reverse"===D||"column-reverse"===D)){var a=i.a;i.a=i.b,i.b=a}if(!o&&t>0){var c=C(t,O);h(c,L),c[n]("mousedown",S.bind(i)),c[n]("touchstart",S.bind(i)),w.insertBefore(c,s.element),i.gutter=c}0===t||t===u.length-1?z(s.element,s.size,L/2):z(s.element,s.size,L);var f=s.element[r]()[y];return f<s.minSize&&(s.minSize=f),t>0&&F.push(i),s}),o?{setSizes:v,destroy:p}:{setSizes:v,getSizes:function(){return E.map(function(e){return e.size})},collapse:function(e){if(e===F.length){var t=F[e-1];g.call(t),o||f.call(t,t.size-t.bGutterSize)}else{var n=F[e];g.call(n),o||f.call(n,n.aGutterSize)}},destroy:p}}});

///////////////////////////////////////////////

///////////////////////////////////////////////
// Copyright (c) 2013 Pieroxy <pieroxy@pieroxy.net>
// This work is free. You can redistribute it and/or modify it
// under the terms of the WTFPL, Version 2
// For more information see LICENSE.txt or http://www.wtfpl.net/
//
// For more information, the home page:
// http://pieroxy.net/blog/pages/lz-string/testing.html
//
// LZ-based compression algorithm, version 1.4.4
var LZString=function(){var o=String.fromCharCode,i={};var n={decompressFromBase64:function(o){return null==o?"":""==o?null:n._decompress(o.length,32,function(n){return function(o,n){if(!i[o]){i[o]={};for(var t=0;t<o.length;t++)i[o][o.charAt(t)]=t}return i[o][n]}("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",o.charAt(n))})},_decompress:function(i,n,t){var r,e,a,s,p,u,l,f=[],c=4,d=4,h=3,v="",g=[],m={val:t(0),position:n,index:1};for(r=0;r<3;r+=1)f[r]=r;for(a=0,p=Math.pow(2,2),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;switch(a){case 0:for(a=0,p=Math.pow(2,8),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;l=o(a);break;case 1:for(a=0,p=Math.pow(2,16),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;l=o(a);break;case 2:return""}for(f[3]=l,e=l,g.push(l);;){if(m.index>i)return"";for(a=0,p=Math.pow(2,h),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;switch(l=a){case 0:for(a=0,p=Math.pow(2,8),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;f[d++]=o(a),l=d-1,c--;break;case 1:for(a=0,p=Math.pow(2,16),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;f[d++]=o(a),l=d-1,c--;break;case 2:return g.join("")}if(0==c&&(c=Math.pow(2,h),h++),f[l])v=f[l];else{if(l!==d)return null;v=e+e.charAt(0)}g.push(v),f[d++]=e+v.charAt(0),e=v,0==--c&&(c=Math.pow(2,h),h++)}}};return n}();"function"==typeof define&&define.amd?define(function(){return LZString}):"undefined"!=typeof module&&null!=module?module.exports=LZString:"undefined"!=typeof angular&&null!=angular&&angular.module("LZString",[]).factory("LZString",function(){return LZString});
///////////////////////////////////////////////

///////////////////////////////////////////////
/*!
 * PEP v0.4.3 | https://github.com/jquery/PEP
 * Copyright jQuery Foundation and other contributors | http://jquery.org/license
 */
! function (a, b) {
  "object" == typeof exports && "undefined" != typeof module ? module.exports = b() : "function" == typeof define && define.amd ? define(b) : a.PointerEventsPolyfill = b()
}(this, function () {
  "use strict";

  function a(a, b) {
    b = b || Object.create(null);
    var c = document.createEvent("Event");
    c.initEvent(a, b.bubbles || !1, b.cancelable || !1);
    for (var d, e = 2; e < m.length; e++) d = m[e], c[d] = b[d] || n[e];
    c.buttons = b.buttons || 0;
    var f = 0;
    return f = b.pressure && c.buttons ? b.pressure : c.buttons ? .5 : 0, c.x = c.clientX, c.y = c.clientY, c.pointerId = b.pointerId || 0, c.width = b.width || 0, c.height = b.height || 0, c.pressure = f, c.tiltX = b.tiltX || 0, c.tiltY = b.tiltY || 0, c.twist = b.twist || 0, c.tangentialPressure = b.tangentialPressure || 0, c.pointerType = b.pointerType || "", c.hwTimestamp = b.hwTimestamp || 0, c.isPrimary = b.isPrimary || !1, c
  }

  function b() {
    this.array = [], this.size = 0
  }

  function c(a, b, c, d) {
    this.addCallback = a.bind(d), this.removeCallback = b.bind(d), this.changedCallback = c.bind(d), A && (this.observer = new A(this.mutationWatcher.bind(this)))
  }

  function d(a) {
    return "body /shadow-deep/ " + e(a)
  }

  function e(a) {
    return '[touch-action="' + a + '"]'
  }

  function f(a) {
    return "{ -ms-touch-action: " + a + "; touch-action: " + a + "; }"
  }

  function g() {
    if (F) {
      D.forEach(function (a) {
        String(a) === a ? (E += e(a) + f(a) + "\n", G && (E += d(a) + f(a) + "\n")) : (E += a.selectors.map(e) + f(a.rule) + "\n", G && (E += a.selectors.map(d) + f(a.rule) + "\n"))
      });
      var a = document.createElement("style");
      a.textContent = E, document.head.appendChild(a)
    }
  }

  function h() {
    if (!window.PointerEvent) {
      if (window.PointerEvent = a, window.navigator.msPointerEnabled) {
        var b = window.navigator.msMaxTouchPoints;
        Object.defineProperty(window.navigator, "maxTouchPoints", {
          value: b,
          enumerable: !0
        }), u.registerSource("ms", _)
      } else Object.defineProperty(window.navigator, "maxTouchPoints", {
        value: 0,
        enumerable: !0
      }), u.registerSource("mouse", N), void 0 !== window.ontouchstart && u.registerSource("touch", V);
      u.register(document)
    }
  }

  function i(a) {
    if (!u.pointermap.has(a)) {
      var b = new Error("InvalidPointerId");
      throw b.name = "InvalidPointerId", b
    }
  }

  function j(a) {
    for (var b = a.parentNode; b && b !== a.ownerDocument;) b = b.parentNode;
    if (!b) {
      var c = new Error("InvalidStateError");
      throw c.name = "InvalidStateError", c
    }
  }

  function k(a) {
    var b = u.pointermap.get(a);
    return 0 !== b.buttons
  }

  function l() {
    window.Element && !Element.prototype.setPointerCapture && Object.defineProperties(Element.prototype, {
      setPointerCapture: {
        value: W
      },
      releasePointerCapture: {
        value: X
      },
      hasPointerCapture: {
        value: Y
      }
    })
  }
  var m = ["bubbles", "cancelable", "view", "detail", "screenX", "screenY", "clientX", "clientY", "ctrlKey", "altKey", "shiftKey", "metaKey", "button", "relatedTarget", "pageX", "pageY"],
    n = [!1, !1, null, null, 0, 0, 0, 0, !1, !1, !1, !1, 0, null, 0, 0],
    o = window.Map && window.Map.prototype.forEach,
    p = o ? Map : b;
  b.prototype = {
    set: function (a, b) {
      return void 0 === b ? this["delete"](a) : (this.has(a) || this.size++, void(this.array[a] = b))
    },
    has: function (a) {
      return void 0 !== this.array[a]
    },
    "delete": function (a) {
      this.has(a) && (delete this.array[a], this.size--)
    },
    get: function (a) {
      return this.array[a]
    },
    clear: function () {
      this.array.length = 0, this.size = 0
    },
    forEach: function (a, b) {
      return this.array.forEach(function (c, d) {
        a.call(b, c, d, this)
      }, this)
    }
  };
  var q = ["bubbles", "cancelable", "view", "detail", "screenX", "screenY", "clientX", "clientY", "ctrlKey", "altKey", "shiftKey", "metaKey", "button", "relatedTarget", "buttons", "pointerId", "width", "height", "pressure", "tiltX", "tiltY", "pointerType", "hwTimestamp", "isPrimary", "type", "target", "currentTarget", "which", "pageX", "pageY", "timeStamp"],
    r = [!1, !1, null, null, 0, 0, 0, 0, !1, !1, !1, !1, 0, null, 0, 0, 0, 0, 0, 0, 0, "", 0, !1, "", null, null, 0, 0, 0, 0],
    s = {
      pointerover: 1,
      pointerout: 1,
      pointerenter: 1,
      pointerleave: 1
    },
    t = "undefined" != typeof SVGElementInstance,
    u = {
      pointermap: new p,
      eventMap: Object.create(null),
      captureInfo: Object.create(null),
      eventSources: Object.create(null),
      eventSourceList: [],
      registerSource: function (a, b) {
        var c = b,
          d = c.events;
        d && (d.forEach(function (a) {
          c[a] && (this.eventMap[a] = c[a].bind(c))
        }, this), this.eventSources[a] = c, this.eventSourceList.push(c))
      },
      register: function (a) {
        for (var b, c = this.eventSourceList.length, d = 0; d < c && (b = this.eventSourceList[d]); d++)
          b.register.call(b, a)
      },
      unregister: function (a) {
        for (var b, c = this.eventSourceList.length, d = 0; d < c && (b = this.eventSourceList[d]); d++)
          b.unregister.call(b, a)
      },
      contains: function (a, b) {
        try {
          return a.contains(b)
        } catch (c) {
          return !1
        }
      },
      down: function (a) {
        a.bubbles = !0, this.fireEvent("pointerdown", a)
      },
      move: function (a) {
        a.bubbles = !0, this.fireEvent("pointermove", a)
      },
      up: function (a) {
        a.bubbles = !0, this.fireEvent("pointerup", a)
      },
      enter: function (a) {
        a.bubbles = !1, this.fireEvent("pointerenter", a)
      },
      leave: function (a) {
        a.bubbles = !1, this.fireEvent("pointerleave", a)
      },
      over: function (a) {
        a.bubbles = !0, this.fireEvent("pointerover", a)
      },
      out: function (a) {
        a.bubbles = !0, this.fireEvent("pointerout", a)
      },
      cancel: function (a) {
        a.bubbles = !0, this.fireEvent("pointercancel", a)
      },
      leaveOut: function (a) {
        this.out(a), this.propagate(a, this.leave, !1)
      },
      enterOver: function (a) {
        this.over(a), this.propagate(a, this.enter, !0)
      },
      eventHandler: function (a) {
        if (!a._handledByPE) {
          var b = a.type,
            c = this.eventMap && this.eventMap[b];
          c && c(a), a._handledByPE = !0
        }
      },
      listen: function (a, b) {
        b.forEach(function (b) {
          this.addEvent(a, b)
        }, this)
      },
      unlisten: function (a, b) {
        b.forEach(function (b) {
          this.removeEvent(a, b)
        }, this)
      },
      addEvent: function (a, b) {
        a.addEventListener(b, this.boundHandler)
      },
      removeEvent: function (a, b) {
        a.removeEventListener(b, this.boundHandler)
      },
      makeEvent: function (b, c) {
        this.captureInfo[c.pointerId] && (c.relatedTarget = null);
        var d = new a(b, c);
        return c.preventDefault && (d.preventDefault = c.preventDefault), d._target = d._target || c.target, d
      },
      fireEvent: function (a, b) {
        var c = this.makeEvent(a, b);
        return this.dispatchEvent(c)
      },
      cloneEvent: function (a) {
        for (var b, c = Object.create(null), d = 0; d < q.length; d++) b = q[d], c[b] = a[b] || r[d], !t || "target" !== b && "relatedTarget" !== b || c[b] instanceof SVGElementInstance && (c[b] = c[b].correspondingUseElement);
        return a.preventDefault && (c.preventDefault = function () {
          a.preventDefault()
        }), c
      },
      getTarget: function (a) {
        var b = this.captureInfo[a.pointerId];
        return b ? a._target !== b && a.type in s ? void 0 : b : a._target
      },
      propagate: function (a, b, c) {
        for (var d = a.target, e = []; d !== document && !d.contains(a.relatedTarget);)
          if (e.push(d), d = d.parentNode, !d) return;
        c && e.reverse(), e.forEach(function (c) {
          a.target = c, b.call(this, a)
        }, this)
      },
      setCapture: function (b, c, d) {
        this.captureInfo[b] && this.releaseCapture(b, d), this.captureInfo[b] = c, this.implicitRelease = this.releaseCapture.bind(this, b, d), document.addEventListener("pointerup", this.implicitRelease), document.addEventListener("pointercancel", this.implicitRelease);
        var e = new a("gotpointercapture");
        e.pointerId = b, e._target = c, d || this.asyncDispatchEvent(e)
      },
      releaseCapture: function (b, c) {
        var d = this.captureInfo[b];
        if (d) {
          this.captureInfo[b] = void 0, document.removeEventListener("pointerup", this.implicitRelease), document.removeEventListener("pointercancel", this.implicitRelease);
          var e = new a("lostpointercapture");
          e.pointerId = b, e._target = d, c || this.asyncDispatchEvent(e)
        }
      },
      dispatchEvent: /*scope.external.dispatchEvent || */ function (a) {
        var b = this.getTarget(a);
        if (b) return b.dispatchEvent(a)
      },
      asyncDispatchEvent: function (a) {
        requestAnimationFrame(this.dispatchEvent.bind(this, a))
      }
    };
  u.boundHandler = u.eventHandler.bind(u);
  var v = {
      shadow: function (a) {
        if (a) return a.shadowRoot || a.webkitShadowRoot
      },
      canTarget: function (a) {
        return a && Boolean(a.elementFromPoint)
      },
      targetingShadow: function (a) {
        var b = this.shadow(a);
        if (this.canTarget(b)) return b
      },
      olderShadow: function (a) {
        var b = a.olderShadowRoot;
        if (!b) {
          var c = a.querySelector("shadow");
          c && (b = c.olderShadowRoot)
        }
        return b
      },
      allShadows: function (a) {
        for (var b = [], c = this.shadow(a); c;) b.push(c), c = this.olderShadow(c);
        return b
      },
      searchRoot: function (a, b, c) {
        if (a) {
          var d, e, f = a.elementFromPoint(b, c);
          for (e = this.targetingShadow(f); e;) {
            if (d = e.elementFromPoint(b, c)) {
              var g = this.targetingShadow(d);
              return this.searchRoot(g, b, c) || d
            }
            e = this.olderShadow(e)
          }
          return f
        }
      },
      owner: function (a) {
        for (var b = a; b.parentNode;) b = b.parentNode;
        return b.nodeType !== Node.DOCUMENT_NODE && b.nodeType !== Node.DOCUMENT_FRAGMENT_NODE && (b = document), b
      },
      findTarget: function (a) {
        var b = a.clientX,
          c = a.clientY,
          d = this.owner(a.target);
        return d.elementFromPoint(b, c) || (d = document), this.searchRoot(d, b, c)
      }
    },
    w = Array.prototype.forEach.call.bind(Array.prototype.forEach),
    x = Array.prototype.map.call.bind(Array.prototype.map),
    y = Array.prototype.slice.call.bind(Array.prototype.slice),
    z = Array.prototype.filter.call.bind(Array.prototype.filter),
    A = window.MutationObserver || window.WebKitMutationObserver,
    B = "[touch-action]",
    C = {
      subtree: !0,
      childList: !0,
      attributes: !0,
      attributeOldValue: !0,
      attributeFilter: ["touch-action"]
    };
  c.prototype = {
    watchSubtree: function (a) {
      //
      this.observer && v.canTarget(a) && this.observer.observe(a, C)
    },
    enableOnSubtree: function (a) {
      this.watchSubtree(a), a === document && "complete" !== document.readyState ? this.installOnLoad() : this.installNewSubtree(a)
    },
    installNewSubtree: function (a) {
      w(this.findElements(a), this.addElement, this)
    },
    findElements: function (a) {
      return a.querySelectorAll ? a.querySelectorAll(B) : []
    },
    removeElement: function (a) {
      this.removeCallback(a)
    },
    addElement: function (a) {
      this.addCallback(a)
    },
    elementChanged: function (a, b) {
      this.changedCallback(a, b)
    },
    concatLists: function (a, b) {
      return a.concat(y(b))
    },
    installOnLoad: function () {
      document.addEventListener("readystatechange", function () {
        "complete" === document.readyState && this.installNewSubtree(document)
      }.bind(this))
    },
    isElement: function (a) {
      return a.nodeType === Node.ELEMENT_NODE
    },
    flattenMutationTree: function (a) {
      var b = x(a, this.findElements, this);
      return b.push(z(a, this.isElement)), b.reduce(this.concatLists, [])
    },
    mutationWatcher: function (a) {
      a.forEach(this.mutationHandler, this)
    },
    mutationHandler: function (a) {
      if ("childList" === a.type) {
        var b = this.flattenMutationTree(a.addedNodes);
        b.forEach(this.addElement, this);
        var c = this.flattenMutationTree(a.removedNodes);
        c.forEach(this.removeElement, this)
      } else "attributes" === a.type && this.elementChanged(a.target, a.oldValue)
    }
  };
  var D = ["none", "auto", "pan-x", "pan-y", {
      rule: "pan-x pan-y",
      selectors: ["pan-x pan-y", "pan-y pan-x"]
    }],
    E = "",
    F = window.PointerEvent || window.MSPointerEvent,
    G = !window.ShadowDOMPolyfill && document.head.createShadowRoot,
    H = u.pointermap,
    I = 25,
    J = [1, 4, 2, 8, 16],
    K = !1;
  try {
    K = 1 === new MouseEvent("test", {
      buttons: 1
    }).buttons
  } catch (L) {}
  var M, N = {
      POINTER_ID: 1,
      POINTER_TYPE: "mouse",
      events: ["mousedown", "mousemove", "mouseup", "mouseover", "mouseout"],
      register: function (a) {
        u.listen(a, this.events)
      },
      unregister: function (a) {
        u.unlisten(a, this.events)
      },
      lastTouches: [],
      isEventSimulatedFromTouch: function (a) {
        for (var b, c = this.lastTouches, d = a.clientX, e = a.clientY, f = 0, g = c.length; f < g && (b = c[f]); f++) {
          var h = Math.abs(d - b.x),
            i = Math.abs(e - b.y);
          if (h <= I && i <= I) return !0
        }
      },
      prepareEvent: function (a) {
        var b = u.cloneEvent(a),
          c = b.preventDefault;
        return b.preventDefault = function () {
          a.preventDefault(), c()
        }, b.pointerId = this.POINTER_ID, b.isPrimary = !0, b.pointerType = this.POINTER_TYPE, b
      },
      prepareButtonsForMove: function (a, b) {
        var c = H.get(this.POINTER_ID);
        0 !== b.which && c ? a.buttons = c.buttons : a.buttons = 0, b.buttons = a.buttons
      },
      mousedown: function (a) {
        if (!this.isEventSimulatedFromTouch(a)) {
          var b = H.get(this.POINTER_ID),
            c = this.prepareEvent(a);
          K || (c.buttons = J[c.button], b && (c.buttons |= b.buttons), a.buttons = c.buttons), H.set(this.POINTER_ID, a), b && 0 !== b.buttons ? u.move(c) : u.down(c)
        }
      },
      mousemove: function (a) {
        if (!this.isEventSimulatedFromTouch(a)) {
          var b = this.prepareEvent(a);
          K || this.prepareButtonsForMove(b, a), b.button = -1, H.set(this.POINTER_ID, a), u.move(b)
        }
      },
      mouseup: function (a) {
        if (!this.isEventSimulatedFromTouch(a)) {
          var b = H.get(this.POINTER_ID),
            c = this.prepareEvent(a);
          if (!K) {
            var d = J[c.button];
            c.buttons = b ? b.buttons & ~d : 0, a.buttons = c.buttons
          }
          H.set(this.POINTER_ID, a),
            c.buttons &= ~J[c.button], 0 === c.buttons ? u.up(c) : u.move(c)
        }
      },
      mouseover: function (a) {
        if (!this.isEventSimulatedFromTouch(a)) {
          var b = this.prepareEvent(a);
          K || this.prepareButtonsForMove(b, a), b.button = -1, H.set(this.POINTER_ID, a), u.enterOver(b)
        }
      },
      mouseout: function (a) {
        if (!this.isEventSimulatedFromTouch(a)) {
          var b = this.prepareEvent(a);
          K || this.prepareButtonsForMove(b, a), b.button = -1, u.leaveOut(b)
        }
      },
      cancel: function (a) {
        var b = this.prepareEvent(a);
        u.cancel(b), this.deactivateMouse()
      },
      deactivateMouse: function () {
        H["delete"](this.POINTER_ID)
      }
    },
    O = u.captureInfo,
    P = v.findTarget.bind(v),
    Q = v.allShadows.bind(v),
    R = u.pointermap,
    S = 2500,
    T = 200,
    U = "touch-action",
    V = {
      events: ["touchstart", "touchmove", "touchend", "touchcancel"],
      register: function (a) {
        M.enableOnSubtree(a)
      },
      unregister: function () {},
      elementAdded: function (a) {
        var b = a.getAttribute(U),
          c = this.touchActionToScrollType(b);
        c && (a._scrollType = c, u.listen(a, this.events),
          Q(a).forEach(function (a) {
            a._scrollType = c, u.listen(a, this.events)
          }, this))
      },
      elementRemoved: function (a) {
        a._scrollType = void 0, u.unlisten(a, this.events),
          Q(a).forEach(function (a) {
            a._scrollType = void 0, u.unlisten(a, this.events)
          }, this)
      },
      elementChanged: function (a, b) {
        var c = a.getAttribute(U),
          d = this.touchActionToScrollType(c),
          e = this.touchActionToScrollType(b);
        d && e ? (a._scrollType = d, Q(a).forEach(function (a) {
          a._scrollType = d
        }, this)) : e ? this.elementRemoved(a) : d && this.elementAdded(a)
      },
      scrollTypes: {
        EMITTER: "none",
        XSCROLLER: "pan-x",
        YSCROLLER: "pan-y",
        SCROLLER: /^(?:pan-x pan-y)|(?:pan-y pan-x)|auto$/
      },
      touchActionToScrollType: function (a) {
        var b = a,
          c = this.scrollTypes;
        return "none" === b ? "none" : b === c.XSCROLLER ? "X" : b === c.YSCROLLER ? "Y" : c.SCROLLER.exec(b) ? "XY" : void 0
      },
      POINTER_TYPE: "touch",
      firstTouch: null,
      isPrimaryTouch: function (a) {
        return this.firstTouch === a.identifier
      },
      setPrimaryTouch: function (a) {
        (0 === R.size || 1 === R.size && R.has(1)) && (this.firstTouch = a.identifier, this.firstXY = {
          X: a.clientX,
          Y: a.clientY
        }, this.scrolling = !1, this.cancelResetClickCount())
      },
      removePrimaryPointer: function (a) {
        a.isPrimary && (this.firstTouch = null, this.firstXY = null, this.resetClickCount())
      },
      clickCount: 0,
      resetId: null,
      resetClickCount: function () {
        var a = function () {
          this.clickCount = 0, this.resetId = null
        }.bind(this);
        this.resetId = setTimeout(a, T)
      },
      cancelResetClickCount: function () {
        this.resetId && clearTimeout(this.resetId)
      },
      typeToButtons: function (a) {
        var b = 0;
        return "touchstart" !== a && "touchmove" !== a || (b = 1), b
      },
      touchToPointer: function (a) {
        var b = this.currentTouchEvent,
          c = u.cloneEvent(a),
          d = c.pointerId = a.identifier + 2;
        c.target = O[d] || P(c), c.bubbles = !0, c.cancelable = !0, c.detail = this.clickCount, c.button = 0, c.buttons = this.typeToButtons(b.type), c.width = 2 * (a.radiusX || a.webkitRadiusX || 0), c.height = 2 * (a.radiusY || a.webkitRadiusY || 0), c.pressure = a.force || a.webkitForce || .5, c.isPrimary = this.isPrimaryTouch(a), c.pointerType = this.POINTER_TYPE,
          c.altKey = b.altKey, c.ctrlKey = b.ctrlKey, c.metaKey = b.metaKey, c.shiftKey = b.shiftKey;
        var e = this;
        return c.preventDefault = function () {
          e.scrolling = !1, e.firstXY = null, b.preventDefault()
        }, c
      },
      processTouches: function (a, b) {
        var c = a.changedTouches;
        this.currentTouchEvent = a;
        for (var d, e = 0; e < c.length; e++) d = c[e], b.call(this, this.touchToPointer(d))
      },
      shouldScroll: function (a) {
        if (this.firstXY) {
          var b, c = a.currentTarget._scrollType;
          if ("none" === c)
            b = !1;
          else if ("XY" === c)
            b = !0;
          else {
            var d = a.changedTouches[0],
              e = c,
              f = "Y" === c ? "X" : "Y",
              g = Math.abs(d["client" + e] - this.firstXY[e]),
              h = Math.abs(d["client" + f] - this.firstXY[f]);
            b = g >= h
          }
          return this.firstXY = null, b
        }
      },
      findTouch: function (a, b) {
        for (var c, d = 0, e = a.length; d < e && (c = a[d]); d++)
          if (c.identifier === b) return !0
      },
      vacuumTouches: function (a) {
        var b = a.touches;
        if (R.size >= b.length) {
          var c = [];
          R.forEach(function (a, d) {
            if (1 !== d && !this.findTouch(b, d - 2)) {
              var e = a.out;
              c.push(e)
            }
          }, this), c.forEach(this.cancelOut, this)
        }
      },
      touchstart: function (a) {
        this.vacuumTouches(a), this.setPrimaryTouch(a.changedTouches[0]), this.dedupSynthMouse(a), this.scrolling || (this.clickCount++, this.processTouches(a, this.overDown))
      },
      overDown: function (a) {
        R.set(a.pointerId, {
          target: a.target,
          out: a,
          outTarget: a.target
        }), u.enterOver(a), u.down(a)
      },
      touchmove: function (a) {
        this.scrolling || (this.shouldScroll(a) ? (this.scrolling = !0, this.touchcancel(a)) : (a.preventDefault(), this.processTouches(a, this.moveOverOut)))
      },
      moveOverOut: function (a) {
        var b = a,
          c = R.get(b.pointerId);
        if (c) {
          var d = c.out,
            e = c.outTarget;
          u.move(b), d && e !== b.target && (d.relatedTarget = b.target, b.relatedTarget = e,
            d.target = e, b.target ? (u.leaveOut(d), u.enterOver(b)) : (
              b.target = e, b.relatedTarget = null, this.cancelOut(b))), c.out = b, c.outTarget = b.target
        }
      },
      touchend: function (a) {
        this.dedupSynthMouse(a), this.processTouches(a, this.upOut)
      },
      upOut: function (a) {
        this.scrolling || (u.up(a), u.leaveOut(a)), this.cleanUpPointer(a)
      },
      touchcancel: function (a) {
        this.processTouches(a, this.cancelOut)
      },
      cancelOut: function (a) {
        u.cancel(a), u.leaveOut(a), this.cleanUpPointer(a)
      },
      cleanUpPointer: function (a) {
        R["delete"](a.pointerId), this.removePrimaryPointer(a)
      },
      dedupSynthMouse: function (a) {
        var b = N.lastTouches,
          c = a.changedTouches[0];
        if (this.isPrimaryTouch(c)) {
          var d = {
            x: c.clientX,
            y: c.clientY
          };
          b.push(d);
          var e = function (a, b) {
            var c = a.indexOf(b);
            c > -1 && a.splice(c, 1)
          }.bind(null, b, d);
          setTimeout(e, S)
        }
      }
    };
  M = new c(V.elementAdded, V.elementRemoved, V.elementChanged, V);
  var W, X, Y, Z = u.pointermap,
    $ = window.MSPointerEvent && "number" == typeof window.MSPointerEvent.MSPOINTER_TYPE_MOUSE,
    _ = {
      events: ["MSPointerDown", "MSPointerMove", "MSPointerUp", "MSPointerOut", "MSPointerOver", "MSPointerCancel", "MSGotPointerCapture", "MSLostPointerCapture"],
      register: function (a) {
        u.listen(a, this.events)
      },
      unregister: function (a) {
        u.unlisten(a, this.events)
      },
      POINTER_TYPES: ["", "unavailable", "touch", "pen", "mouse"],
      prepareEvent: function (a) {
        var b = a;
        return $ && (b = u.cloneEvent(a), b.pointerType = this.POINTER_TYPES[a.pointerType]), b
      },
      cleanup: function (a) {
        Z["delete"](a)
      },
      MSPointerDown: function (a) {
        Z.set(a.pointerId, a);
        var b = this.prepareEvent(a);
        u.down(b)
      },
      MSPointerMove: function (a) {
        var b = this.prepareEvent(a);
        u.move(b)
      },
      MSPointerUp: function (a) {
        var b = this.prepareEvent(a);
        u.up(b), this.cleanup(a.pointerId)
      },
      MSPointerOut: function (a) {
        var b = this.prepareEvent(a);
        u.leaveOut(b)
      },
      MSPointerOver: function (a) {
        var b = this.prepareEvent(a);
        u.enterOver(b)
      },
      MSPointerCancel: function (a) {
        var b = this.prepareEvent(a);
        u.cancel(b), this.cleanup(a.pointerId)
      },
      MSLostPointerCapture: function (a) {
        var b = u.makeEvent("lostpointercapture", a);
        u.dispatchEvent(b)
      },
      MSGotPointerCapture: function (a) {
        var b = u.makeEvent("gotpointercapture", a);
        u.dispatchEvent(b)
      }
    },
    aa = window.navigator;
  aa.msPointerEnabled ? (W = function (a) {
    i(a), j(this), k(a) && (u.setCapture(a, this, !0), this.msSetPointerCapture(a))
  }, X = function (a) {
    i(a), u.releaseCapture(a, !0), this.msReleasePointerCapture(a)
  }) : (W = function (a) {
    i(a), j(this), k(a) && u.setCapture(a, this)
  }, X = function (a) {
    i(a), u.releaseCapture(a)
  }), Y = function (a) {
    return !!u.captureInfo[a]
  }, g(), h(), l();
  var ba = {
    dispatcher: u,
    Installer: c,
    PointerEvent: a,
    PointerMap: p,
    targetFinding: v
  };
  return ba
});
///////////////////////////////////////////////

///////////////////////////////////////////////
var config = {"show_fabrication":false,"redraw_on_drag":true,"highlight_pin1":false,"extra_fields":["BOM_Manufacturer","BOM_Manufacturer Part","BOM_Supplier","BOM_Supplier Part"],"dark_mode":false,"bom_view":"left-right","board_rotation":0,"checkboxes":"Sourced,Placed","show_silkscreen":true,"show_pads":true,"layer_view":"FB"};
///////////////////////////////////////////////

///////////////////////////////////////////////
var pcbdata = {"ibom_version":"v2.3-50-g53ae\n","edges_bbox":{"minx":3896.9,"miny":3369.2,"maxx":4145.1,"maxy":3434.7},"edges":[{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4118.3466,3387.5591 4105.5513,3387.5591 4105.5513,3384.8032 3908.7009,3384.8032"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M3908.7009,3384.8032 3908.7009,3420.2362 4105.5513,3420.2362 4105.5513,3417.4803 4118.3466,3417.4803"},{"type":"arc","width":1,"svgpath":"M 4118.3466 3417.4803 A 14.9606 14.9606 0 1 0 4118.3466 3387.5591","net":""},{"type":"circle","start":[3955.827,3402.5],"radius":9.8425,"width":0.5},{"type":"circle","start":[3955.945,3402.52],"radius":9.8425,"width":0.5}],"drawings":{"silkscreen":{"F":[{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M3909.8637,3418.4451 3909.8637,3386.5559"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M3929.5487,3386.5559 3929.5487,3394.1189"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M3929.5487,3410.8989 3929.5487,3418.4451"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M3929.5487,3418.4451 3923.7687,3418.4451"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M3914.0688,3418.4451 3909.8637,3418.4451"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M3929.5487,3386.5559 3923.7687,3386.5559"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M3914.0688,3386.5559 3909.8637,3386.5559"},{"type":"circle","_svgpath":"M 3931.26, 3393.012 m -0.501, 0 a 0.501,0.501 0 1,0 1.002,0 a 0.501,0.501 0 1,0 -1.002,0","start":[3931.26,3393.012],"radius":0.501,"width":"1","net":""},{"type":"text","svgpath":"M 3896.8227 3401.6761 L 3901.1127 3401.6761 M 3896.8227 3403.1061 L 3896.8227 3400.2461 M 3896.8227 3398.8961 L 3901.1127 3398.8961 M 3896.8227 3396.0261 L 3901.1127 3396.0261 M 3898.8627 3398.8961 L 3898.8627 3396.0261 M 3896.8227 3394.6761 L 3901.1127 3394.6761 M 3896.8227 3394.6761 L 3896.8227 3393.2461 L 3897.0227 3392.6361 L 3897.4327 3392.2261 L 3897.8427 3392.0161 L 3898.4627 3391.8161 L 3899.4827 3391.8161 L 3900.0927 3392.0161 L 3900.5027 3392.2261 L 3900.9127 3392.6361 L 3901.1127 3393.2461 L 3901.1127 3394.6761 M 3896.8227 3389.2361 L 3897.0227 3389.8561 L 3897.6427 3390.2561 L 3898.6627 3390.4661 L 3899.2727 3390.4661 L 3900.3027 3390.2561 L 3900.9127 3389.8561 L 3901.1127 3389.2361 L 3901.1127 3388.8261 L 3900.9127 3388.2161 L 3900.3027 3387.8061 L 3899.2727 3387.6061 L 3898.6627 3387.6061 L 3897.6427 3387.8061 L 3897.0227 3388.2161 L 3896.8227 3388.8261 L 3896.8227 3389.2361 M 3896.8227 3383.7961 L 3896.8227 3385.8461 L 3898.6627 3386.0461 L 3898.4627 3385.8461 L 3898.2527 3385.2261 L 3898.2527 3384.6161 L 3898.4627 3384.0061 L 3898.8627 3383.5961 L 3899.4827 3383.3861 L 3899.8927 3383.3861 L 3900.5027 3383.5961 L 3900.9127 3384.0061 L 3901.1127 3384.6161 L 3901.1127 3385.2261 L 3900.9127 3385.8461 L 3900.7127 3386.0461 L 3900.3027 3386.2561 M 3897.6427 3382.0361 L 3897.4327 3381.6261 L 3896.8227 3381.0161 L 3901.1127 3381.0161 M 3896.8227 3377.2061 L 3896.8227 3379.2561 L 3898.6627 3379.4561 L 3898.4627 3379.2561 L 3898.2527 3378.6461 L 3898.2527 3378.0261 L 3898.4627 3377.4161 L 3898.8627 3377.0061 L 3899.4827 3376.8061 L 3899.8927 3376.8061 L 3900.5027 3377.0061 L 3900.9127 3377.4161 L 3901.1127 3378.0261 L 3901.1127 3378.6461 L 3900.9127 3379.2561 L 3900.7127 3379.4561 L 3900.3027 3379.6661 M 3899.2727 3375.4561 L 3899.2727 3371.7661 M 3896.8227 3369.1961 L 3897.0227 3369.8061 L 3897.6427 3370.2161 L 3898.6627 3370.4161 L 3899.2727 3370.4161 L 3900.3027 3370.2161 L 3900.9127 3369.8061 L 3901.1127 3369.1961 L 3901.1127 3368.7861 L 3900.9127 3368.1661 L 3900.3027 3367.7561 L 3899.2727 3367.5561 L 3898.6627 3367.5561 L 3897.6427 3367.7561 L 3897.0227 3368.1661 L 3896.8227 3368.7861 L 3896.8227 3369.1961 M 3896.8227 3365.1861 L 3897.0227 3365.7961 L 3897.4327 3366.0061 L 3897.8427 3366.0061 L 3898.2527 3365.7961 L 3898.4627 3365.3861 L 3898.6627 3364.5661 L 3898.8627 3363.9561 L 3899.2727 3363.5461 L 3899.6827 3363.3461 L 3900.3027 3363.3461 L 3900.7127 3363.5461 L 3900.9127 3363.7561 L 3901.1127 3364.3661 L 3901.1127 3365.1861 L 3900.9127 3365.7961 L 3900.7127 3366.0061 L 3900.3027 3366.2061 L 3899.6827 3366.2061 L 3899.2727 3366.0061 L 3898.8627 3365.5961 L 3898.6627 3364.9761 L 3898.4627 3364.1561 L 3898.2527 3363.7561 L 3897.8427 3363.5461 L 3897.4327 3363.5461 L 3897.0227 3363.7561 L 3896.8227 3364.3661 L 3896.8227 3365.1861 M 3897.8427 3358.9261 L 3897.4327 3359.1261 L 3897.0227 3359.5361 L 3896.8227 3359.9461 L 3896.8227 3360.7661 L 3897.0227 3361.1761 L 3897.4327 3361.5861 L 3897.8427 3361.7861 L 3898.4627 3361.9961 L 3899.4827 3361.9961 L 3900.0927 3361.7861 L 3900.5027 3361.5861 L 3900.9127 3361.1761 L 3901.1127 3360.7661 L 3901.1127 3359.9461 L 3900.9127 3359.5361 L 3900.5027 3359.1261 L 3900.0927 3358.9261 M 3896.8227 3357.5761 L 3901.1127 3357.5761 M 3901.1127 3357.5761 L 3901.1127 3355.1161 M 3899.2727 3353.7661 L 3899.2727 3350.0861 M 3897.4327 3345.8761 L 3897.0227 3346.2861 L 3896.8227 3346.8961 L 3896.8227 3347.7161 L 3897.0227 3348.3261 L 3897.4327 3348.7361 L 3897.8427 3348.7361 L 3898.2527 3348.5361 L 3898.4627 3348.3261 L 3898.6627 3347.9161 L 3899.0727 3346.6961 L 3899.2727 3346.2861 L 3899.4827 3346.0761 L 3899.8927 3345.8761 L 3900.5027 3345.8761 L 3900.9127 3346.2861 L 3901.1127 3346.8961 L 3901.1127 3347.7161 L 3900.9127 3348.3261 L 3900.5027 3348.7361 M 3896.8227 3344.5261 L 3901.1127 3344.5261 M 3896.8227 3344.5261 L 3901.1127 3341.6561 M 3896.8227 3341.6561 L 3901.1127 3341.6561","useTrueTypeFontRendering":false,"thickness":0.6,"width":0.6,"net":"","val":1},{"type":"text","svgpath":"M 3913.4917 3405.3876 L 3917.7817 3407.0276 M 3913.4917 3405.3876 L 3917.7817 3403.7576 M 3916.3517 3406.4176 L 3916.3517 3404.3676 M 3913.4917 3402.4076 L 3917.7817 3402.4076 M 3913.4917 3402.4076 L 3913.4917 3400.5676 L 3913.6917 3399.9476 L 3913.9017 3399.7476 L 3914.3117 3399.5376 L 3914.7217 3399.5376 L 3915.1317 3399.7476 L 3915.3317 3399.9476 L 3915.5317 3400.5676 L 3915.5317 3402.4076 M 3915.5317 3400.9776 L 3917.7817 3399.5376 M 3913.4917 3398.1876 L 3917.7817 3398.1876 M 3913.4917 3398.1876 L 3917.7817 3396.5576 M 3913.4917 3394.9176 L 3917.7817 3396.5576 M 3913.4917 3394.9176 L 3917.7817 3394.9176","useTrueTypeFontRendering":false,"thickness":0.6,"width":0.6,"net":"","ref":1},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4007.992,3398.1694 4007.992,3391.87"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4000.7061,3391.8901 3995.785,3391.8901"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4000.7061,3398.1895 3995.785,3398.1895"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4003.0706,3391.87 4007.992,3391.87"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4003.0706,3398.1694 4007.992,3398.1694"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M3995.785,3398.1895 3995.785,3391.8901"},{"type":"text","svgpath":"M 3996.8424 3407.5895 L 3997.2524 3407.3795 L 3997.8624 3406.7695 L 3997.8624 3411.0595 M 4001.6724 3406.7695 L 3999.6224 3406.7695 L 3999.4224 3408.6095 L 3999.6224 3408.4095 L 4000.2424 3408.1995 L 4000.8524 3408.1995 L 4001.4624 3408.4095 L 4001.8724 3408.8095 L 4002.0824 3409.4295 L 4002.0824 3409.8395 L 4001.8724 3410.4495 L 4001.4624 3410.8595 L 4000.8524 3411.0595 L 4000.2424 3411.0595 L 3999.6224 3410.8595 L 3999.4224 3410.6595 L 3999.2124 3410.2495","useTrueTypeFontRendering":false,"thickness":0.6,"width":0.6,"net":"","val":1},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4007.992,3405.6497 4007.992,3399.3503"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4000.7061,3399.3704 3995.785,3399.3704"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4000.7061,3405.6698 3995.785,3405.6698"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4003.0706,3399.3503 4007.992,3399.3503"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4003.0706,3405.6497 4007.992,3405.6497"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M3995.785,3405.6698 3995.785,3399.3704"},{"type":"text","svgpath":"M 3996.8423 3415.0701 L 3997.2523 3414.8601 L 3997.8623 3414.2501 L 3997.8623 3418.5401 M 4001.6723 3414.2501 L 3999.6223 3414.2501 L 3999.4223 3416.0901 L 3999.6223 3415.8901 L 4000.2423 3415.6801 L 4000.8523 3415.6801 L 4001.4623 3415.8901 L 4001.8723 3416.2901 L 4002.0823 3416.9101 L 4002.0823 3417.3201 L 4001.8723 3417.9301 L 4001.4623 3418.3401 L 4000.8523 3418.5401 L 4000.2423 3418.5401 L 3999.6223 3418.3401 L 3999.4223 3418.1401 L 3999.2123 3417.7301","useTrueTypeFontRendering":false,"thickness":0.6,"width":0.6,"net":"","val":1},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4007.992,3413.13 4007.992,3406.8306"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4000.7061,3406.8507 3995.785,3406.8507"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4000.7061,3413.1501 3995.785,3413.1501"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4003.0706,3406.8306 4007.992,3406.8306"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4003.0706,3413.13 4007.992,3413.13"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M3995.785,3413.1501 3995.785,3406.8507"},{"type":"text","svgpath":"M 3991.8923 3422.3404 L 3991.6923 3421.9304 L 3991.0823 3421.7304 L 3990.6723 3421.7304 L 3990.0523 3421.9304 L 3989.6423 3422.5504 L 3989.4423 3423.5704 L 3989.4423 3424.5904 L 3989.6423 3425.4104 L 3990.0523 3425.8204 L 3990.6723 3426.0204 L 3990.8723 3426.0204 L 3991.4923 3425.8204 L 3991.8923 3425.4104 L 3992.1023 3424.8004 L 3992.1023 3424.5904 L 3991.8923 3423.9804 L 3991.4923 3423.5704 L 3990.8723 3423.3704 L 3990.6723 3423.3704 L 3990.0523 3423.5704 L 3989.6423 3423.9804 L 3989.4423 3424.5904 M 3995.9023 3422.3404 L 3995.7023 3421.9304 L 3995.0923 3421.7304 L 3994.6823 3421.7304 L 3994.0623 3421.9304 L 3993.6523 3422.5504 L 3993.4523 3423.5704 L 3993.4523 3424.5904 L 3993.6523 3425.4104 L 3994.0623 3425.8204 L 3994.6823 3426.0204 L 3994.8823 3426.0204 L 3995.4923 3425.8204 L 3995.9023 3425.4104 L 3996.1123 3424.8004 L 3996.1123 3424.5904 L 3995.9023 3423.9804 L 3995.4923 3423.5704 L 3994.8823 3423.3704 L 3994.6823 3423.3704 L 3994.0623 3423.5704 L 3993.6523 3423.9804 L 3993.4523 3424.5904 M 3997.6623 3425.0004 L 3997.4623 3425.2104 L 3997.6623 3425.4104 L 3997.8723 3425.2104 L 3997.6623 3425.0004 M 4001.6723 3421.7304 L 3999.6323 3421.7304 L 3999.4223 3423.5704 L 3999.6323 3423.3704 L 4000.2423 3423.1604 L 4000.8523 3423.1604 L 4001.4723 3423.3704 L 4001.8823 3423.7704 L 4002.0823 3424.3904 L 4002.0823 3424.8004 L 4001.8823 3425.4104 L 4001.4723 3425.8204 L 4000.8523 3426.0204 L 4000.2423 3426.0204 L 3999.6323 3425.8204 L 3999.4223 3425.6204 L 3999.2223 3425.2104","useTrueTypeFontRendering":false,"thickness":0.6,"width":0.6,"net":"","val":1},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4128.2725,3392.6575 4128.2725,3412.3424"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4108.5875,3392.6575 4108.5875,3412.3425"},{"type":"text","svgpath":"M 4117.1651 3379.0749 L 4117.1651 3383.3649 M 4117.1651 3383.3649 L 4119.6151 3383.3649 M 4122.3951 3379.0749 L 4122.3951 3383.3649 M 4120.9651 3379.0749 L 4123.8251 3379.0749 M 4128.0451 3379.6849 L 4127.6351 3379.2749 L 4127.0151 3379.0749 L 4126.2051 3379.0749 L 4125.5851 3379.2749 L 4125.1751 3379.6849 L 4125.1751 3380.0949 L 4125.3851 3380.5049 L 4125.5851 3380.7149 L 4125.9951 3380.9149 L 4127.2251 3381.3249 L 4127.6351 3381.5249 L 4127.8351 3381.7349 L 4128.0451 3382.1449 L 4128.0451 3382.7549 L 4127.6351 3383.1649 L 4127.0151 3383.3649 L 4126.2051 3383.3649 L 4125.5851 3383.1649 L 4125.1751 3382.7549 M 4130.8251 3379.0749 L 4130.8251 3383.3649 M 4129.3951 3379.0749 L 4132.2551 3379.0749 M 4133.6051 3381.5249 L 4137.2851 3381.5249 M 4138.6351 3379.0749 L 4138.6351 3383.3649 M 4141.5051 3379.0749 L 4138.6351 3381.9349 M 4139.6651 3380.9149 L 4141.5051 3383.3649 M 4145.3051 3379.0749 L 4143.2651 3379.0749 L 4143.0551 3380.9149 L 4143.2651 3380.7149 L 4143.8751 3380.5049 L 4144.4851 3380.5049 L 4145.1051 3380.7149 L 4145.5151 3381.1149 L 4145.7151 3381.7349 L 4145.7151 3382.1449 L 4145.5151 3382.7549 L 4145.1051 3383.1649 L 4144.4851 3383.3649 L 4143.8751 3383.3649 L 4143.2651 3383.1649 L 4143.0551 3382.9649 L 4142.8551 3382.5549 M 4149.5151 3379.6849 L 4149.3151 3379.2749 L 4148.7051 3379.0749 L 4148.2951 3379.0749 L 4147.6751 3379.2749 L 4147.2651 3379.8949 L 4147.0651 3380.9149 L 4147.0651 3381.9349 L 4147.2651 3382.7549 L 4147.6751 3383.1649 L 4148.2951 3383.3649 L 4148.4951 3383.3649 L 4149.1151 3383.1649 L 4149.5151 3382.7549 L 4149.7251 3382.1449 L 4149.7251 3381.9349 L 4149.5151 3381.3249 L 4149.1151 3380.9149 L 4148.4951 3380.7149 L 4148.2951 3380.7149 L 4147.6751 3380.9149 L 4147.2651 3381.3249 L 4147.0651 3381.9349 M 4151.4851 3379.0749 L 4153.7351 3379.0749 L 4152.5051 3380.7149 L 4153.1151 3380.7149 L 4153.5251 3380.9149 L 4153.7351 3381.1149 L 4153.9351 3381.7349 L 4153.9351 3382.1449 L 4153.7351 3382.7549 L 4153.3251 3383.1649 L 4152.7151 3383.3649 L 4152.0951 3383.3649 L 4151.4851 3383.1649 L 4151.2751 3382.9649 L 4151.0751 3382.5549 M 4155.2851 3379.0749 L 4155.2851 3383.3649 M 4155.2851 3379.0749 L 4157.1251 3379.0749 L 4157.7451 3379.2749 L 4157.9451 3379.4849 L 4158.1551 3379.8949 L 4158.1551 3380.3049 L 4157.9451 3380.7149 L 4157.7451 3380.9149 L 4157.1251 3381.1149 M 4155.2851 3381.1149 L 4157.1251 3381.1149 L 4157.7451 3381.3249 L 4157.9451 3381.5249 L 4158.1551 3381.9349 L 4158.1551 3382.5549 L 4157.9451 3382.9649 L 4157.7451 3383.1649 L 4157.1251 3383.3649 L 4155.2851 3383.3649 M 4162.5651 3380.0949 L 4162.3651 3379.6849 L 4161.9551 3379.2749 L 4161.5451 3379.0749 L 4160.7251 3379.0749 L 4160.3151 3379.2749 L 4159.9151 3379.6849 L 4159.7051 3380.0949 L 4159.5051 3380.7149 L 4159.5051 3381.7349 L 4159.7051 3382.3449 L 4159.9151 3382.7549 L 4160.3151 3383.1649 L 4160.7251 3383.3649 L 4161.5451 3383.3649 L 4161.9551 3383.1649 L 4162.3651 3382.7549 L 4162.5651 3382.3449 L 4162.5651 3381.7349 M 4161.5451 3381.7349 L 4162.5651 3381.7349 M 4163.9151 3379.0749 L 4163.9151 3383.3649 M 4163.9151 3379.0749 L 4166.5751 3379.0749 M 4163.9151 3381.1149 L 4165.5551 3381.1149 M 4163.9151 3383.3649 L 4166.5751 3383.3649 M 4167.9251 3379.0749 L 4168.9551 3383.3649 M 4169.9751 3379.0749 L 4168.9551 3383.3649 M 4169.9751 3379.0749 L 4170.9951 3383.3649 M 4172.0151 3379.0749 L 4170.9951 3383.3649","useTrueTypeFontRendering":false,"thickness":0.6,"width":0.6,"net":"","val":1},{"type":"circle","_svgpath":"M 3955.827, 3402.5 m -11.811, 0 a 11.811,11.811 0 1,0 23.622,0 a 11.811,11.811 0 1,0 -23.622,0","start":[3955.827,3402.5],"radius":11.811,"width":"1","net":""},{"type":"text","svgpath":"M 4039.9638 3405.6984 C 4040.5738 3405.6984 4041.1338 3405.5484 4041.6538 3405.2384 L 4041.4638 3404.8384 C 4041.0538 3405.0784 4040.5638 3405.2484 4040.0138 3405.2484 C 4038.4738 3405.2484 4037.3438 3404.2384 4037.3438 3402.5084 C 4037.3438 3400.4084 4038.8938 3399.0484 4040.4838 3399.0484 C 4042.0838 3399.0484 4042.9538 3400.0884 4042.9538 3401.5584 C 4042.9538 3402.7384 4042.2938 3403.4284 4041.7338 3403.4284 C 4041.2338 3403.4284 4041.0538 3403.0784 4041.2438 3402.3484 L 4041.5838 3400.5984 L 4041.1338 3400.5984 L 4041.0538 3400.9584 L 4041.0338 3400.9584 C 4040.8638 3400.6584 4040.6138 3400.5184 4040.3138 3400.5184 C 4039.2638 3400.5184 4038.6138 3401.6384 4038.6138 3402.5684 C 4038.6138 3403.3784 4039.0738 3403.8284 4039.6638 3403.8284 C 4040.0638 3403.8284 4040.4538 3403.5484 4040.7538 3403.2084 L 4040.7738 3403.2084 C 4040.8238 3403.6684 4041.1938 3403.8784 4041.6838 3403.8784 C 4042.4738 3403.8784 4043.4238 3403.0784 4043.4238 3401.5284 C 4043.4238 3399.7884 4042.3138 3398.5984 4040.5338 3398.5984 C 4038.5738 3398.5984 4036.8538 3400.1584 4036.8538 3402.5284 C 4036.8538 3404.5784 4038.2138 3405.6984 4039.9638 3405.6984 Z  M 4039.7838 3403.3584 C 4039.4138 3403.3584 4039.1338 3403.1284 4039.1338 3402.5284 C 4039.1338 3401.8384 4039.5838 3400.9884 4040.3038 3400.9884 C 4040.5638 3400.9884 4040.7338 3401.0784 4040.9038 3401.3684 L 4040.6538 3402.8184 C 4040.3338 3403.1984 4040.0538 3403.3584 4039.7838 3403.3584 Z  M 4044.6438 3404.3384 L 4045.2938 3404.3384 L 4045.2938 3401.5184 C 4045.5838 3400.7684 4046.0338 3400.4984 4046.4038 3400.4984 C 4046.5738 3400.4984 4046.6638 3400.5184 4046.8138 3400.5684 L 4046.9338 3399.9984 C 4046.7938 3399.9284 4046.6638 3399.9084 4046.4838 3399.9084 C 4045.9938 3399.9084 4045.5538 3400.2684 4045.2538 3400.8084 L 4045.2338 3400.8084 L 4045.1738 3400.0184 L 4044.6438 3400.0184 Z  M 4049.2338 3404.4384 C 4050.2938 3404.4384 4051.2238 3403.6084 4051.2238 3402.1784 C 4051.2238 3400.7384 4050.2938 3399.9084 4049.2338 3399.9084 C 4048.1738 3399.9084 4047.2538 3400.7384 4047.2538 3402.1784 C 4047.2538 3403.6084 4048.1738 3404.4384 4049.2338 3404.4384 Z  M 4049.2338 3403.8984 C 4048.4538 3403.8984 4047.9238 3403.2084 4047.9238 3402.1784 C 4047.9238 3401.1584 4048.4538 3400.4584 4049.2338 3400.4584 C 4050.0138 3400.4584 4050.5538 3401.1584 4050.5538 3402.1784 C 4050.5538 3403.2084 4050.0138 3403.8984 4049.2338 3403.8984 Z  M 4054.2538 3404.4384 C 4055.2538 3404.4384 4056.1338 3403.5884 4056.1338 3402.1084 C 4056.1338 3400.7784 4055.5338 3399.9084 4054.4138 3399.9084 C 4053.9138 3399.9084 4053.4238 3400.1884 4053.0138 3400.5284 L 4053.0538 3399.7384 L 4053.0538 3397.9684 L 4052.4038 3397.9684 L 4052.4038 3404.3384 L 4052.9138 3404.3384 L 4052.9838 3403.8884 L 4053.0038 3403.8884 C 4053.3838 3404.2384 4053.8538 3404.4384 4054.2538 3404.4384 Z  M 4054.1538 3403.8884 C 4053.8538 3403.8884 4053.4538 3403.7684 4053.0538 3403.4184 L 4053.0538 3401.0784 C 4053.4838 3400.6784 4053.8838 3400.4584 4054.2538 3400.4584 C 4055.1238 3400.4584 4055.4538 3401.1384 4055.4538 3402.1184 C 4055.4538 3403.2084 4054.9038 3403.8884 4054.1538 3403.8884 Z  M 4058.9638 3404.4384 C 4060.0138 3404.4384 4060.9538 3403.6084 4060.9538 3402.1784 C 4060.9538 3400.7384 4060.0138 3399.9084 4058.9638 3399.9084 C 4057.9038 3399.9084 4056.9738 3400.7384 4056.9738 3402.1784 C 4056.9738 3403.6084 4057.9038 3404.4384 4058.9638 3404.4384 Z  M 4058.9638 3403.8984 C 4058.1838 3403.8984 4057.6538 3403.2084 4057.6538 3402.1784 C 4057.6538 3401.1584 4058.1838 3400.4584 4058.9638 3400.4584 C 4059.7338 3400.4584 4060.2838 3401.1584 4060.2838 3402.1784 C 4060.2838 3403.2084 4059.7338 3403.8984 4058.9638 3403.8984 Z  M 4063.2938 3404.4384 C 4063.5338 3404.4384 4063.8238 3404.3584 4064.0638 3404.2784 L 4063.9338 3403.7884 C 4063.7938 3403.8484 4063.5938 3403.9084 4063.4338 3403.9084 C 4062.9138 3403.9084 4062.7538 3403.5884 4062.7538 3403.0584 L 4062.7538 3400.5584 L 4063.9438 3400.5584 L 4063.9438 3400.0184 L 4062.7538 3400.0184 L 4062.7538 3398.7984 L 4062.2138 3398.7984 L 4062.1338 3400.0184 L 4061.4538 3400.0584 L 4061.4538 3400.5584 L 4062.1038 3400.5584 L 4062.1038 3403.0384 C 4062.1038 3403.8784 4062.3938 3404.4384 4063.2938 3404.4384 Z  M 4066.2738 3404.4384 C 4066.8038 3404.4384 4067.2638 3404.1584 4067.6138 3403.8184 L 4067.6338 3403.8184 L 4067.6938 3404.3384 L 4068.2238 3404.3384 L 4068.2238 3397.9684 L 4067.5738 3397.9684 L 4067.5738 3399.6684 L 4067.6138 3400.4184 C 4067.2138 3400.0984 4066.8838 3399.9084 4066.3738 3399.9084 C 4065.3738 3399.9084 4064.4938 3400.7884 4064.4938 3402.1784 C 4064.4938 3403.6284 4065.1838 3404.4384 4066.2738 3404.4384 Z  M 4066.4138 3403.8884 C 4065.6138 3403.8884 4065.1738 3403.2384 4065.1738 3402.1784 C 4065.1738 3401.1684 4065.7338 3400.4584 4066.4738 3400.4584 C 4066.8538 3400.4584 4067.1838 3400.5984 4067.5738 3400.9384 L 4067.5738 3403.2784 C 4067.1938 3403.6784 4066.8238 3403.8884 4066.4138 3403.8884 Z  M 4071.3838 3404.4384 C 4072.4438 3404.4384 4073.3738 3403.6084 4073.3738 3402.1784 C 4073.3738 3400.7384 4072.4438 3399.9084 4071.3838 3399.9084 C 4070.3338 3399.9084 4069.4038 3400.7384 4069.4038 3402.1784 C 4069.4038 3403.6084 4070.3338 3404.4384 4071.3838 3404.4384 Z  M 4071.3838 3403.8984 C 4070.6138 3403.8984 4070.0738 3403.2084 4070.0738 3402.1784 C 4070.0738 3401.1584 4070.6138 3400.4584 4071.3838 3400.4584 C 4072.1638 3400.4584 4072.7038 3401.1584 4072.7038 3402.1784 C 4072.7038 3403.2084 4072.1638 3403.8984 4071.3838 3403.8984 Z  M 4075.2538 3404.4384 C 4075.4438 3404.4384 4075.5438 3404.4184 4075.6538 3404.3884 L 4075.5538 3403.8784 C 4075.4738 3403.8984 4075.4438 3403.8984 4075.4038 3403.8984 C 4075.2938 3403.8984 4075.2038 3403.8084 4075.2038 3403.5984 L 4075.2038 3397.9684 L 4074.5538 3397.9684 L 4074.5538 3403.5484 C 4074.5538 3404.1184 4074.7638 3404.4384 4075.2538 3404.4384 Z  M 4076.7938 3406.2284 C 4077.6338 3406.2284 4078.0838 3405.5584 4078.3738 3404.7384 L 4080.0138 3400.0184 L 4079.3738 3400.0184 L 4078.5738 3402.4884 C 4078.4538 3402.8684 4078.3238 3403.3184 4078.2038 3403.7084 L 4078.1638 3403.7084 C 4078.0138 3403.3184 4077.8638 3402.8684 4077.7338 3402.4884 L 4076.8138 3400.0184 L 4076.1338 3400.0184 L 4077.8738 3404.3584 L 4077.7738 3404.6984 C 4077.5838 3405.2584 4077.2638 3405.6784 4076.7738 3405.6784 C 4076.6538 3405.6784 4076.5238 3405.6384 4076.4338 3405.6084 L 4076.2938 3406.1384 C 4076.4338 3406.1984 4076.6038 3406.2284 4076.7938 3406.2284 Z ","useTrueTypeFontRendering":true,"thickness":0.8,"width":0.8,"net":""},{"type":"text","svgpath":"M4047.05 3413L4047.53 3413L4048.49 3410.30L4048.09 3410.30L4047.55 3411.87C4047.47 3412.14 4047.39 3412.41 4047.30 3412.66L4047.28 3412.66C4047.20 3412.41 4047.11 3412.14 4047.03 3411.87L4046.49 3410.30L4046.07 3410.30ZM4049 3413L4050.97 3413L4050.97 3412.66L4050.22 3412.66L4050.22 3409.34L4049.91 3409.34C4049.72 3409.45 4049.49 3409.53 4049.17 3409.59L4049.17 3409.86L4049.82 3409.86L4049.82 3412.66L4049 3412.66ZM4051.97 3413.07C4052.14 3413.07 4052.28 3412.93 4052.28 3412.74C4052.28 3412.55 4052.14 3412.41 4051.97 3412.41C4051.80 3412.41 4051.66 3412.55 4051.66 3412.74C4051.66 3412.93 4051.80 3413.07 4051.97 3413.07ZM4052.87 3413L4055.14 3413L4055.14 3412.65L4054.09 3412.65C4053.90 3412.65 4053.68 3412.66 4053.49 3412.68C4054.38 3411.84 4054.96 3411.09 4054.96 3410.34C4054.96 3409.70 4054.56 3409.28 4053.91 3409.28C4053.46 3409.28 4053.14 3409.49 4052.85 3409.81L4053.09 3410.04C4053.30 3409.80 4053.56 3409.61 4053.87 3409.61C4054.34 3409.61 4054.56 3409.93 4054.56 3410.36C4054.56 3410.99 4054.04 3411.74 4052.87 3412.76ZM4055.62 3413L4057.88 3413L4057.88 3412.65L4056.83 3412.65C4056.65 3412.65 4056.43 3412.66 4056.23 3412.68C4057.13 3411.84 4057.71 3411.09 4057.71 3410.34C4057.71 3409.70 4057.31 3409.28 4056.66 3409.28C4056.20 3409.28 4055.89 3409.49 4055.59 3409.81L4055.83 3410.04C4056.04 3409.80 4056.31 3409.61 4056.61 3409.61C4057.09 3409.61 4057.31 3409.93 4057.31 3410.36C4057.31 3410.99 4056.79 3411.74 4055.62 3412.76ZM4058.58 3413L4060.55 3413L4060.55 3412.66L4059.81 3412.66L4059.81 3409.34L4059.50 3409.34C4059.31 3409.45 4059.07 3409.53 4058.76 3409.59L4058.76 3409.86L4059.40 3409.86L4059.40 3412.66L4058.58 3412.66ZM4061.11 3413L4063.37 3413L4063.37 3412.65L4062.32 3412.65C4062.14 3412.65 4061.92 3412.66 4061.72 3412.68C4062.62 3411.84 4063.20 3411.09 4063.20 3410.34C4063.20 3409.70 4062.80 3409.28 4062.15 3409.28C4061.69 3409.28 4061.38 3409.49 4061.08 3409.81L4061.32 3410.04C4061.53 3409.80 4061.80 3409.61 4062.10 3409.61C4062.58 3409.61 4062.80 3409.93 4062.80 3410.36C4062.80 3410.99 4062.28 3411.74 4061.11 3412.76Z","useTrueTypeFontRendering":true,"thickness":0.8,"width":0.8,"net":""}],"B":[{"type":"text","svgpath":"M 4071.2608 3400.2283 L 4069.8008 3400.2283 C 4068.0208 3400.2283 4067.0908 3399.1183 4067.0908 3397.2783 C 4067.0908 3395.4383 4068.0208 3394.3683 4069.8208 3394.3683 L 4071.2608 3394.3683 Z  M 4070.6008 3399.6883 L 4070.6008 3394.9183 L 4069.8808 3394.9183 C 4068.4508 3394.9183 4067.7708 3395.7783 4067.7708 3397.2783 C 4067.7708 3398.7683 4068.4508 3399.6883 4069.8808 3399.6883 Z  M 4063.6708 3400.3283 C 4062.2108 3400.3283 4061.1908 3399.1483 4061.1908 3397.2783 C 4061.1908 3395.4083 4062.2108 3394.2683 4063.6708 3394.2683 C 4065.1208 3394.2683 4066.1408 3395.4083 4066.1408 3397.2783 C 4066.1408 3399.1483 4065.1208 3400.3283 4063.6708 3400.3283 Z  M 4063.6708 3399.7483 C 4064.7508 3399.7483 4065.4608 3398.7783 4065.4608 3397.2783 C 4065.4608 3395.7783 4064.7508 3394.8483 4063.6708 3394.8483 C 4062.5808 3394.8483 4061.8708 3395.7783 4061.8708 3397.2783 C 4061.8708 3398.7783 4062.5808 3399.7483 4063.6708 3399.7483 Z  M 4059.9008 3400.2283 L 4056.6508 3400.2283 L 4056.6508 3399.6683 L 4059.2408 3399.6683 L 4059.2408 3394.3683 L 4059.9008 3394.3683 Z  M 4055.3308 3400.2283 L 4054.6808 3400.2283 L 4054.6808 3397.9283 L 4052.9208 3394.3683 L 4053.6008 3394.3683 L 4054.3908 3396.0683 C 4054.5708 3396.4983 4054.7708 3396.9083 4054.9808 3397.3483 L 4055.0108 3397.3483 C 4055.2308 3396.9083 4055.4108 3396.4983 4055.6108 3396.0683 L 4056.3908 3394.3683 L 4057.0908 3394.3683 L 4055.3308 3397.9283 Z  M 4071.2608 3412.2283 L 4068.0108 3412.2283 L 4068.0108 3411.6683 L 4070.6008 3411.6683 L 4070.6008 3406.3683 L 4071.2608 3406.3683 Z  M 4067.0308 3412.2283 L 4066.3808 3412.2283 L 4066.3808 3407.9083 L 4067.0308 3407.9083 Z  M 4066.7008 3406.9783 C 4066.4508 3406.9783 4066.2508 3406.8083 4066.2508 3406.5283 C 4066.2508 3406.2583 4066.4508 3406.0883 4066.7008 3406.0883 C 4066.9708 3406.0883 4067.1608 3406.2583 4067.1608 3406.5283 C 4067.1608 3406.8083 4066.9708 3406.9783 4066.7008 3406.9783 Z  M 4064.8808 3412.2283 L 4064.2308 3412.2283 L 4064.2308 3409.0683 C 4063.8208 3408.5983 4063.4408 3408.3683 4063.1008 3408.3683 C 4062.5208 3408.3683 4062.2508 3408.7283 4062.2508 3409.5683 L 4062.2508 3412.2283 L 4061.6108 3412.2283 L 4061.6108 3409.0683 C 4061.1908 3408.5983 4060.8208 3408.3683 4060.4708 3408.3683 C 4059.8908 3408.3683 4059.6308 3408.7283 4059.6308 3409.5683 L 4059.6308 3412.2283 L 4058.9708 3412.2283 L 4058.9708 3409.4883 C 4058.9708 3408.3783 4059.4108 3407.7983 4060.2908 3407.7983 C 4060.8108 3407.7983 4061.2608 3408.1383 4061.7308 3408.6383 C 4061.8908 3408.1283 4062.2508 3407.7983 4062.9208 3407.7983 C 4063.4208 3407.7983 4063.8908 3408.1283 4064.2608 3408.5483 L 4064.2908 3408.5483 L 4064.3508 3407.9083 L 4064.8808 3407.9083 Z  M 4057.5308 3412.2283 L 4056.8808 3412.2283 L 4056.8808 3407.9083 L 4057.5308 3407.9083 Z  M 4057.2008 3406.9783 C 4056.9408 3406.9783 4056.7508 3406.8083 4056.7508 3406.5283 C 4056.7508 3406.2583 4056.9408 3406.0883 4057.2008 3406.0883 C 4057.4708 3406.0883 4057.6508 3406.2583 4057.6508 3406.5283 C 4057.6508 3406.8083 4057.4708 3406.9783 4057.2008 3406.9783 Z  M 4054.0608 3412.3283 C 4053.8208 3412.3283 4053.5308 3412.2483 4053.2908 3412.1683 L 4053.4208 3411.6783 C 4053.5708 3411.7383 4053.7708 3411.7983 4053.9308 3411.7983 C 4054.4508 3411.7983 4054.6108 3411.4783 4054.6108 3410.9483 L 4054.6108 3408.4483 L 4053.4108 3408.4483 L 4053.4108 3407.9083 L 4054.6108 3407.9083 L 4054.6108 3406.6883 L 4055.1508 3406.6883 L 4055.2308 3407.9083 L 4055.9108 3407.9483 L 4055.9108 3408.4483 L 4055.2508 3408.4483 L 4055.2508 3410.9283 C 4055.2508 3411.7683 4054.9708 3412.3283 4054.0608 3412.3283 Z  M 4050.5708 3412.3283 C 4049.5708 3412.3283 4048.6908 3411.4783 4048.6908 3409.9983 C 4048.6908 3408.6683 4049.2908 3407.7983 4050.4108 3407.7983 C 4050.9108 3407.7983 4051.4008 3408.0783 4051.8108 3408.4183 L 4051.7708 3407.6283 L 4051.7708 3405.8583 L 4052.4208 3405.8583 L 4052.4208 3412.2283 L 4051.9108 3412.2283 L 4051.8408 3411.7783 L 4051.8208 3411.7783 C 4051.4408 3412.1283 4050.9708 3412.3283 4050.5708 3412.3283 Z  M 4050.6708 3411.7783 C 4050.9708 3411.7783 4051.3708 3411.6583 4051.7708 3411.3083 L 4051.7708 3408.9683 C 4051.3408 3408.5683 4050.9408 3408.3483 4050.5708 3408.3483 C 4049.7008 3408.3483 4049.3708 3409.0283 4049.3708 3410.0083 C 4049.3708 3411.0983 4049.9208 3411.7783 4050.6708 3411.7783 Z  M 4047.5108 3412.2283 L 4046.8608 3412.2283 L 4046.8608 3407.9083 L 4047.5108 3407.9083 Z  M 4047.1808 3406.9783 C 4046.9308 3406.9783 4046.7308 3406.8083 4046.7308 3406.5283 C 4046.7308 3406.2583 4046.9308 3406.0883 4047.1808 3406.0883 C 4047.4508 3406.0883 4047.6408 3406.2583 4047.6408 3406.5283 C 4047.6408 3406.8083 4047.4508 3406.9783 4047.1808 3406.9783 Z  M 4044.0508 3412.3283 C 4043.8108 3412.3283 4043.5208 3412.2483 4043.2808 3412.1683 L 4043.4108 3411.6783 C 4043.5508 3411.7383 4043.7508 3411.7983 4043.9108 3411.7983 C 4044.4308 3411.7983 4044.5908 3411.4783 4044.5908 3410.9483 L 4044.5908 3408.4483 L 4043.4008 3408.4483 L 4043.4008 3407.9083 L 4044.5908 3407.9083 L 4044.5908 3406.6883 L 4045.1308 3406.6883 L 4045.2108 3407.9083 L 4045.8908 3407.9483 L 4045.8908 3408.4483 L 4045.2408 3408.4483 L 4045.2408 3410.9283 C 4045.2408 3411.7683 4044.9508 3412.3283 4044.0508 3412.3283 Z  M 4039.3408 3412.2283 L 4038.6708 3412.2283 L 4038.6708 3406.9283 L 4036.8808 3406.9283 L 4036.8808 3406.3683 L 4041.1308 3406.3683 L 4041.1308 3406.9283 L 4039.3408 3406.9283 Z  M 4034.7308 3412.3283 C 4034.1308 3412.3283 4033.6908 3412.1283 4033.3208 3411.8883 L 4033.5608 3411.4583 C 4033.8808 3411.6783 4034.2308 3411.8083 4034.6508 3411.8083 C 4035.5208 3411.8083 4036.0908 3411.1683 4036.1308 3410.2083 L 4033.1808 3410.2083 C 4033.1608 3410.1083 4033.1508 3409.9683 4033.1508 3409.8183 C 4033.1508 3408.5683 4033.7708 3407.7983 4034.8508 3407.7983 C 4035.8508 3407.7983 4036.7808 3408.6683 4036.7808 3410.0683 C 4036.7808 3411.4883 4035.8708 3412.3283 4034.7308 3412.3283 Z  M 4036.1408 3409.7283 C 4036.0508 3408.8483 4035.4908 3408.3283 4034.8508 3408.3283 C 4034.1408 3408.3283 4033.7308 3408.8083 4033.7308 3409.7283 Z  M 4030.3808 3412.3283 C 4029.8508 3412.3283 4029.3708 3412.1283 4028.9908 3411.7783 L 4029.2808 3411.3483 C 4029.5508 3411.5883 4029.9208 3411.7883 4030.3308 3411.7883 C 4031.1608 3411.7883 4031.7208 3411.0983 4031.7208 3410.0683 C 4031.7208 3409.0483 4031.1208 3408.3483 4030.3108 3408.3483 C 4029.9508 3408.3483 4029.6708 3408.5083 4029.4108 3408.7383 L 4029.0708 3408.3183 C 4029.3708 3408.0383 4029.7708 3407.7983 4030.3308 3407.7983 C 4031.4308 3407.7983 4032.3908 3408.6283 4032.3908 3410.0683 C 4032.3908 3411.4983 4031.5208 3412.3283 4030.3808 3412.3283 Z  M 4028.0108 3412.2283 L 4027.3608 3412.2283 L 4027.3608 3409.0683 C 4026.9108 3408.6083 4026.5808 3408.3683 4026.1308 3408.3683 C 4025.5308 3408.3683 4025.2708 3408.7283 4025.2708 3409.5683 L 4025.2708 3412.2283 L 4024.6108 3412.2283 L 4024.6108 3409.4883 C 4024.6108 3408.3783 4025.0308 3407.7983 4025.9308 3407.7983 C 4026.5308 3407.7983 4026.9708 3408.1283 4027.3808 3408.5283 L 4027.3608 3407.6283 L 4027.3608 3405.8583 L 4028.0108 3405.8583 Z  M 4023.1908 3412.2283 L 4022.5408 3412.2283 L 4022.5408 3409.0683 C 4022.0908 3408.6083 4021.7708 3408.3683 4021.3108 3408.3683 C 4020.7108 3408.3683 4020.4508 3408.7283 4020.4508 3409.5683 L 4020.4508 3412.2283 L 4019.8008 3412.2283 L 4019.8008 3409.4883 C 4019.8008 3408.3783 4020.2108 3407.7983 4021.1208 3407.7983 C 4021.7108 3407.7983 4022.1608 3408.1283 4022.5708 3408.5383 L 4022.6008 3408.5383 L 4022.6608 3407.9083 L 4023.1908 3407.9083 Z  M 4016.7008 3412.3283 C 4015.6508 3412.3283 4014.7108 3411.4983 4014.7108 3410.0683 C 4014.7108 3408.6283 4015.6508 3407.7983 4016.7008 3407.7983 C 4017.7608 3407.7983 4018.6908 3408.6283 4018.6908 3410.0683 C 4018.6908 3411.4983 4017.7608 3412.3283 4016.7008 3412.3283 Z  M 4016.7008 3411.7883 C 4017.4808 3411.7883 4018.0108 3411.0983 4018.0108 3410.0683 C 4018.0108 3409.0483 4017.4808 3408.3483 4016.7008 3408.3483 C 4015.9308 3408.3483 4015.3808 3409.0483 4015.3808 3410.0683 C 4015.3808 3411.0983 4015.9308 3411.7883 4016.7008 3411.7883 Z  M 4012.8308 3412.3283 C 4012.6508 3412.3283 4012.5408 3412.3083 4012.4408 3412.2783 L 4012.5308 3411.7683 C 4012.6108 3411.7883 4012.6508 3411.7883 4012.6908 3411.7883 C 4012.8008 3411.7883 4012.8908 3411.6983 4012.8908 3411.4883 L 4012.8908 3405.8583 L 4013.5308 3405.8583 L 4013.5308 3411.4383 C 4013.5308 3412.0083 4013.3308 3412.3283 4012.8308 3412.3283 Z  M 4009.6508 3412.3283 C 4008.6008 3412.3283 4007.6608 3411.4983 4007.6608 3410.0683 C 4007.6608 3408.6283 4008.6008 3407.7983 4009.6508 3407.7983 C 4010.7108 3407.7983 4011.6408 3408.6283 4011.6408 3410.0683 C 4011.6408 3411.4983 4010.7108 3412.3283 4009.6508 3412.3283 Z  M 4009.6508 3411.7883 C 4010.4308 3411.7883 4010.9708 3411.0983 4010.9708 3410.0683 C 4010.9708 3409.0483 4010.4308 3408.3483 4009.6508 3408.3483 C 4008.8808 3408.3483 4008.3308 3409.0483 4008.3308 3410.0683 C 4008.3308 3411.0983 4008.8808 3411.7883 4009.6508 3411.7883 Z  M 4005.0508 3414.2383 C 4003.7308 3414.2383 4002.8908 3413.5383 4002.8908 3412.7583 C 4002.8908 3412.0483 4003.3908 3411.7283 4004.3808 3411.7283 L 4005.2508 3411.7283 C 4005.8408 3411.7283 4006.0108 3411.5283 4006.0108 3411.2483 C 4006.0108 3410.9883 4005.8908 3410.8383 4005.7308 3410.6983 C 4005.5308 3410.8083 4005.2808 3410.8583 4005.0608 3410.8583 C 4004.1908 3410.8583 4003.5008 3410.2683 4003.5008 3409.3483 C 4003.5008 3408.9583 4003.6608 3408.6183 4003.8808 3408.4083 L 4002.9708 3408.4083 L 4002.9708 3407.9083 L 4004.4608 3407.9083 C 4004.6108 3407.8483 4004.8208 3407.7983 4005.0608 3407.7983 C 4005.9308 3407.7983 4006.6608 3408.4083 4006.6608 3409.3283 C 4006.6608 3409.8483 4006.3808 3410.2783 4006.1008 3410.4983 L 4006.1008 3410.5283 C 4006.3208 3410.6883 4006.5808 3410.9683 4006.5808 3411.3283 C 4006.5808 3411.6783 4006.4108 3411.9083 4006.1908 3412.0483 L 4006.1908 3412.0783 C 4006.5908 3412.3383 4006.8308 3412.6983 4006.8308 3413.0683 C 4006.8308 3413.8083 4006.1108 3414.2383 4005.0508 3414.2383 Z  M 4005.0608 3410.4083 C 4005.5808 3410.4083 4006.0308 3409.9883 4006.0308 3409.3283 C 4006.0308 3408.6783 4005.6008 3408.2883 4005.0608 3408.2883 C 4004.5308 3408.2883 4004.0908 3408.6883 4004.0908 3409.3283 C 4004.0908 3409.9883 4004.5408 3410.4083 4005.0608 3410.4083 Z  M 4004.9608 3413.7783 C 4005.7708 3413.7783 4006.2508 3413.4683 4006.2508 3412.9883 C 4006.2508 3412.7283 4006.1108 3412.4483 4005.7808 3412.2083 C 4005.5808 3412.2583 4005.3708 3412.2883 4005.2308 3412.2883 L 4004.4408 3412.2883 C 4003.8508 3412.2883 4003.5308 3412.4283 4003.5308 3412.8483 C 4003.5308 3413.3183 4004.0908 3413.7783 4004.9608 3413.7783 Z  M 4001.8808 3414.1183 C 4001.0408 3414.1183 4000.5908 3413.4483 4000.3008 3412.6283 L 3998.6508 3407.9083 L 3999.2908 3407.9083 L 4000.1008 3410.3783 C 4000.2208 3410.7583 4000.3508 3411.2083 4000.4708 3411.5983 L 4000.5108 3411.5983 C 4000.6508 3411.2083 4000.8108 3410.7583 4000.9408 3410.3783 L 4001.8608 3407.9083 L 4002.5408 3407.9083 L 4000.8008 3412.2483 L 4000.8908 3412.5883 C 4001.0908 3413.1483 4001.4108 3413.5683 4001.9008 3413.5683 C 4002.0208 3413.5683 4002.1508 3413.5283 4002.2408 3413.4983 L 4002.3708 3414.0283 C 4002.2408 3414.0883 4002.0708 3414.1183 4001.8808 3414.1183 Z  M 3995.9508 3412.2283 L 3995.2908 3412.2283 L 3995.2908 3406.3683 L 3995.9508 3406.3683 Z  M 3993.7108 3412.2283 L 3993.0608 3412.2283 L 3993.0608 3409.0683 C 3992.6108 3408.6083 3992.2908 3408.3683 3991.8308 3408.3683 C 3991.2308 3408.3683 3990.9708 3408.7283 3990.9708 3409.5683 L 3990.9708 3412.2283 L 3990.3208 3412.2283 L 3990.3208 3409.4883 C 3990.3208 3408.3783 3990.7308 3407.7983 3991.6408 3407.7983 C 3992.2308 3407.7983 3992.6808 3408.1283 3993.0908 3408.5383 L 3993.1208 3408.5383 L 3993.1808 3407.9083 L 3993.7108 3407.9083 Z  M 3987.2008 3412.3283 C 3986.6708 3412.3283 3986.1908 3412.1283 3985.8108 3411.7783 L 3986.0908 3411.3483 C 3986.3708 3411.5883 3986.7308 3411.7883 3987.1408 3411.7883 C 3987.9708 3411.7883 3988.5308 3411.0983 3988.5308 3410.0683 C 3988.5308 3409.0483 3987.9308 3408.3483 3987.1308 3408.3483 C 3986.7708 3408.3483 3986.4908 3408.5083 3986.2308 3408.7383 L 3985.8908 3408.3183 C 3986.1908 3408.0383 3986.5808 3407.7983 3987.1508 3407.7983 C 3988.2508 3407.7983 3989.2108 3408.6283 3989.2108 3410.0683 C 3989.2108 3411.4983 3988.3308 3412.3283 3987.2008 3412.3283 Z  M 3984.5108 3412.3283 C 3984.2408 3412.3283 3984.0108 3412.1283 3984.0108 3411.8183 C 3984.0108 3411.4983 3984.2408 3411.2883 3984.5108 3411.2883 C 3984.7708 3411.2883 3985.0008 3411.4983 3985.0008 3411.8183 C 3985.0008 3412.1283 3984.7708 3412.3283 3984.5108 3412.3283 Z ","useTrueTypeFontRendering":true,"thickness":0.8,"width":0.8,"net":""}]},"fabrication":{"F":[],"B":[]}},"footprints":[{"ref":"ARM","center":[3923.9372,3402.5001],"bbox":{"pos":[3923.9372,3402.5001],"angle":90,"relpos":[-19.88289999999961,-14.073499999999967],"size":[39.76499999999987,21.89730000000054]},"pads":[{"layers":["F"],"pos":[3928.955,3395.619],"size":[1.1811,5.1181],"angle":-270,"pin1":1,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":3928.9552,"y":3395.6193},"polygon":[{"x":3931.5142,"y":3395.0288},{"x":3931.5142,"y":3396.2099},{"x":3926.3961,"y":3396.2099},{"x":3926.3961,"y":3395.0288}],"net":"+3.3V"},{"layers":["F"],"pos":[3928.955,3397.587],"size":[1.1811,5.1181],"angle":-270,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":3928.9551,"y":3397.5873},"polygon":[{"x":3931.5142,"y":3396.9968},{"x":3931.5142,"y":3398.1779},{"x":3926.3961,"y":3398.1779},{"x":3926.3961,"y":3396.9968}],"net":"+3.3V"},{"layers":["F"],"pos":[3928.955,3399.556],"size":[1.1811,5.1181],"angle":-270,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":3928.9551,"y":3399.5563},"polygon":[{"x":3931.5142,"y":3398.9658},{"x":3931.5142,"y":3400.1469},{"x":3926.3961,"y":3400.1469},{"x":3926.3961,"y":3398.9658}],"net":"ARM_B"},{"layers":["F"],"pos":[3928.955,3401.524],"size":[1.1811,5.1181],"angle":-270,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":3928.9551,"y":3401.5243},"polygon":[{"x":3931.5142,"y":3400.9338},{"x":3931.5142,"y":3402.1149},{"x":3926.3961,"y":3402.1149},{"x":3926.3961,"y":3400.9338}],"net":"ARM_B"},{"layers":["F"],"pos":[3928.955,3403.493],"size":[1.1811,5.1181],"angle":-270,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":3928.9551,"y":3403.4933},"polygon":[{"x":3931.5142,"y":3402.9028},{"x":3931.5142,"y":3404.0839},{"x":3926.3961,"y":3404.0839},{"x":3926.3961,"y":3402.9028}],"net":"ARM_G"},{"layers":["F"],"pos":[3928.955,3405.461],"size":[1.1811,5.1181],"angle":-270,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":3928.9551,"y":3405.4613},"polygon":[{"x":3931.5142,"y":3404.8708},{"x":3931.5142,"y":3406.0519},{"x":3926.3961,"y":3406.0519},{"x":3926.3961,"y":3404.8708}],"net":"ARM_G"},{"layers":["F"],"pos":[3928.955,3407.43],"size":[1.1811,5.1181],"angle":-270,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":3928.9551,"y":3407.4303},"polygon":[{"x":3931.5142,"y":3406.8398},{"x":3931.5142,"y":3408.0209},{"x":3926.3961,"y":3408.0209},{"x":3926.3961,"y":3406.8398}],"net":"ARM_R"},{"layers":["F"],"pos":[3928.955,3409.398],"size":[1.1811,5.1181],"angle":-270,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":3928.9552,"y":3409.3983},"polygon":[{"x":3931.5143,"y":3408.8078},{"x":3931.5143,"y":3409.9889},{"x":3926.3962,"y":3409.9889},{"x":3926.3962,"y":3408.8078}],"net":"ARM_R"},{"layers":["F"],"pos":[3918.919,3418.446],"size":[5.9055,7.874],"angle":-270,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":3918.9183,"y":3418.4453},"polygon":[{"x":3922.8588,"y":3415.4905},{"x":3922.8588,"y":3421.4005},{"x":3914.9788,"y":3421.4005},{"x":3914.9788,"y":3415.4905}],"net":""},{"layers":["F"],"pos":[3918.919,3386.555],"size":[5.9055,7.874],"angle":-270,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":3918.9183,"y":3386.5553},"polygon":[{"x":3922.8588,"y":3383.5999},{"x":3922.8588,"y":3389.5099},{"x":3914.9788,"y":3389.5099},{"x":3914.9788,"y":3383.5999}],"net":""}],"drawings":[],"layer":"F"},{"ref":"R1","center":[4001.8898,3395.0197],"bbox":{"pos":[4001.8898,3395.0197],"angle":180,"relpos":[-6.102200000000721,-3.169799999999668],"size":[12.207000000000335,6.319500000000062]},"pads":[{"layers":["F"],"pos":[3998.74,3395.02],"size":[3.937,4.3307],"angle":-180,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":3998.7403,"y":3395.0197},"polygon":[{"x":4000.7084,"y":3397.1851},{"x":3996.7714,"y":3397.1851},{"x":3996.7714,"y":3392.8543},{"x":4000.7084,"y":3392.8543}],"net":"ARM_B"},{"layers":["F"],"pos":[4005.039,3395.02],"size":[3.937,4.3307],"angle":-180,"pin1":1,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4005.0391,"y":3395.0197},"polygon":[{"x":4007.0076,"y":3397.1851},{"x":4003.0706,"y":3397.1851},{"x":4003.0706,"y":3392.8543},{"x":4007.0076,"y":3392.8543}],"net":"LED1_3"}],"drawings":[],"layer":"F"},{"ref":"R2","center":[4001.8898,3402.5],"bbox":{"pos":[4001.8898,3402.5],"angle":180,"relpos":[-6.102200000000721,-3.169799999999668],"size":[12.207000000000335,6.319500000000062]},"pads":[{"layers":["F"],"pos":[3998.74,3402.5],"size":[3.937,4.3307],"angle":-180,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":3998.7403,"y":3402.5},"polygon":[{"x":4000.7084,"y":3404.6654},{"x":3996.7714,"y":3404.6654},{"x":3996.7714,"y":3400.3346},{"x":4000.7084,"y":3400.3346}],"net":"ARM_G"},{"layers":["F"],"pos":[4005.039,3402.5],"size":[3.937,4.3307],"angle":-180,"pin1":1,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4005.0391,"y":3402.5},"polygon":[{"x":4007.0076,"y":3404.6654},{"x":4003.0706,"y":3404.6654},{"x":4003.0706,"y":3400.3346},{"x":4007.0076,"y":3400.3346}],"net":"R2_1"}],"drawings":[],"layer":"F"},{"ref":"R3","center":[4001.8898,3409.9803],"bbox":{"pos":[4001.8898,3409.9803],"angle":180,"relpos":[-6.102200000000721,-3.1697999999992135],"size":[12.207000000000335,6.319500000000062]},"pads":[{"layers":["F"],"pos":[3998.74,3409.98],"size":[3.937,4.3307],"angle":-180,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":3998.7403,"y":3409.9803},"polygon":[{"x":4000.7084,"y":3412.1457},{"x":3996.7714,"y":3412.1457},{"x":3996.7714,"y":3407.8149},{"x":4000.7084,"y":3407.8149}],"net":"ARM_R"},{"layers":["F"],"pos":[4005.039,3409.98],"size":[3.937,4.3307],"angle":-180,"pin1":1,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4005.0391,"y":3409.9803},"polygon":[{"x":4007.0076,"y":3412.1457},{"x":4003.0706,"y":3412.1457},{"x":4003.0706,"y":3407.8149},{"x":4007.0076,"y":3407.8149}],"net":"LED1_1"}],"drawings":[],"layer":"F"},{"ref":"LED1","center":[4118.43,3402.5],"bbox":{"pos":[4118.43,3402.5],"angle":0,"relpos":[-9.842500000000655,-11.417050000000017],"size":[19.6850000000004,22.83410000000049]},"pads":[{"layers":["F"],"pos":[4112.131,3393.642],"size":[5.1181,5.1181],"angle":0,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4112.1308,"y":3393.6417},"polygon":[{"x":4109.5717,"y":3391.0827},{"x":4114.6899,"y":3391.0827},{"x":4114.6899,"y":3396.2008},{"x":4109.5717,"y":3396.2008}],"net":"LED1_3"},{"layers":["F"],"pos":[4118.43,3393.642],"size":[5.1181,5.1181],"angle":0,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4118.43,"y":3393.6417},"polygon":[{"x":4115.871,"y":3391.0827},{"x":4120.9892,"y":3391.0827},{"x":4120.9892,"y":3396.2008},{"x":4115.871,"y":3396.2008}],"net":"R2_1"},{"layers":["F"],"pos":[4124.729,3393.642],"size":[5.1181,5.1181],"angle":0,"pin1":1,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4124.7292,"y":3393.6417},"polygon":[{"x":4122.1701,"y":3391.0827},{"x":4127.2883,"y":3391.0827},{"x":4127.2883,"y":3396.2008},{"x":4122.1701,"y":3396.2008}],"net":"LED1_1"},{"layers":["F"],"pos":[4124.729,3411.358],"size":[5.1181,5.1181],"angle":0,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4124.7292,"y":3411.3583},"polygon":[{"x":4122.1701,"y":3408.7994},{"x":4127.2883,"y":3408.7994},{"x":4127.2883,"y":3413.9174},{"x":4122.1701,"y":3413.9174}],"net":"+3.3V"},{"layers":["F"],"pos":[4118.43,3411.358],"size":[5.1181,5.1181],"angle":0,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4118.43,"y":3411.3583},"polygon":[{"x":4115.871,"y":3408.7994},{"x":4120.9892,"y":3408.7994},{"x":4120.9892,"y":3413.9174},{"x":4115.871,"y":3413.9174}],"net":"+3.3V"},{"layers":["F"],"pos":[4112.131,3411.358],"size":[5.1181,5.1181],"angle":0,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4112.1308,"y":3411.3583},"polygon":[{"x":4109.5717,"y":3408.7994},{"x":4114.6899,"y":3408.7994},{"x":4114.6899,"y":3413.9174},{"x":4109.5717,"y":3413.9174}],"net":"+3.3V"}],"drawings":[],"layer":"F"}],"metadata":{"title":"Doly_Arm","revision":"0","company":"Unknown Company","date":"2023-04-03"},"tracks":{"F":[{"type":"polyline","net":"ARM_B","start":[0,0],"end":[0,0],"width":1,"svgpath":"M3928.9547,3399.5561 3928.9547,3401.5241"},{"type":"polyline","net":"ARM_G","start":[0,0],"end":[0,0],"width":1,"svgpath":"M3928.9547,3403.4931 3928.9547,3405.4611"},{"type":"polyline","net":"ARM_R","start":[0,0],"end":[0,0],"width":1,"svgpath":"M3928.95,3407.43 3928.96,3409.4"},{"type":"polyline","net":"LED1_3","start":[0,0],"end":[0,0],"width":3.937,"svgpath":"M4005.0392,3395.0192 4006.4164,3393.642 4112.131,3393.642"},{"type":"polyline","net":"R2_1","start":[0,0],"end":[0,0],"width":3.937,"svgpath":"M4005.04,3402.5 4009.96,3402.5 4012.72,3399.74 4116.06,3399.74 4118.43,3397.38 4118.43,3393.6"},{"type":"polyline","net":"+3.3V","start":[0,0],"end":[0,0],"width":3.937,"svgpath":"M4124.729,3411.358 4112.131,3411.358"},{"type":"polyline","net":"+3.3V","start":[0,0],"end":[0,0],"width":1,"svgpath":"M3928.95,3395.62 3922.75,3395.62 3920.2,3398.17"},{"type":"polyline","net":"+3.3V","start":[0,0],"end":[0,0],"width":1,"svgpath":"M3928.9547,3397.5871 3924.1341,3397.5871 3924.1341,3397.7759 3920.2,3401.71"},{"type":"polyline","net":"+3.3V","start":[0,0],"end":[0,0],"width":3.937,"svgpath":"M4098.9371,3411.358 4098.9371,3411.358 4096.5746,3413.7205"},{"type":"polyline","net":"+3.3V","start":[0,0],"end":[0,0],"width":3.937,"svgpath":"M4112.131,3411.358 4098.9371,3411.358"},{"type":"polyline","net":"+3.3V","start":[0,0],"end":[0,0],"width":2.3622,"svgpath":"M3920.2,3398.17 3920.2,3398.17 3920.2,3401.71"},{"type":"polyline","net":"ARM_B","start":[0,0],"end":[0,0],"width":1,"svgpath":"M3998.74,3395.02 3995.39,3398.37 3977.48,3398.37 3966.26,3387.15 3948.34,3387.15 3934,3401.5 3928.95,3401.5"},{"type":"polyline","net":"LED1_1","start":[0,0],"end":[0,0],"width":3.937,"svgpath":"M4005.04,3409.98 4010.35,3409.98 4015.08,3405.26 4118.23,3405.26 4124.73,3398.75 4124.73,3393.64"},{"type":"polyline","net":"ARM_R","start":[0,0],"end":[0,0],"width":1,"svgpath":"M3928.96,3409.4 3928.96,3407.42 3939.29,3407.42 3947.76,3415.89 3965.37,3415.89 3974.62,3406.63 3995.39,3406.63 3998.74,3409.98"},{"type":"polyline","net":"ARM_G","start":[0,0],"end":[0,0],"width":1,"svgpath":"M3928.95,3403.49 3935.35,3403.49 3949.33,3389.51 3964.49,3389.51 3977.48,3402.5 3998.74,3402.5"},{"start":[4096.575,3413.721],"end":[4096.575,3413.721],"width":2.4,"net":"+3.3V"},{"start":[3920.2,3401.71],"end":[3920.2,3401.71],"width":2.4,"net":"+3.3V"},{"start":[4098.937,3411.358],"end":[4098.937,3411.358],"width":2.4,"net":"+3.3V"},{"start":[3920.2,3398.17],"end":[3920.2,3398.17],"width":2.4,"net":"+3.3V"},{"start":[3982.008,3391.28],"end":[3982.008,3391.28],"width":2.4,"net":"+3.3V"}],"B":[{"type":"polyline","net":"+3.3V","start":[0,0],"end":[0,0],"width":3.937,"svgpath":"M4098.9371,3411.358 4093.82,3416.48 3934.96,3416.48 3920.2,3401.71"},{"start":[4096.575,3413.721],"end":[4096.575,3413.721],"width":2.4,"net":"+3.3V"},{"start":[3920.2,3401.71],"end":[3920.2,3401.71],"width":2.4,"net":"+3.3V"},{"start":[4098.937,3411.358],"end":[4098.937,3411.358],"width":2.4,"net":"+3.3V"},{"start":[3920.2,3398.17],"end":[3920.2,3398.17],"width":2.4,"net":"+3.3V"},{"start":[3982.008,3391.28],"end":[3982.008,3391.28],"width":2.4,"net":"+3.3V"}]},"zones":{"F":[{"net":"+3.3V","svgpath":"M 3978.27529 3396.85 L 3978.12222 3396.81955 3977.99245 3396.73284 3967.76565 3386.50604 3967.67894 3386.37627 3967.64849 3386.2232 3967.67894 3386.07013 3967.76565 3385.94036 3967.89542 3385.85365 3968.04849 3385.8232 4104.1313 3385.8232 4104.28437 3385.85365 4104.41414 3385.94036 4104.50085 3386.07013 4104.5313 3386.2232 4104.5313 3387.5349 4104.56234 3387.79053 4104.64501 3388.00852 4104.78439 3388.21044 4104.90426 3388.33032 4105.04542 3388.43418 4105.2636 3388.53237 4105.55734 3388.5791 4118.32157 3388.5791 4119.37868 3388.61853 4120.39065 3388.73096 4121.39171 3388.91695 4122.37652 3389.17551 4122.69474 3389.28407 4122.83252 3389.36474 4122.92777 3389.49287 4122.96532 3389.64804 4122.9392 3389.80554 4122.85355 3389.94028 4122.72203 3390.03079 4122.56559 3390.06265 4122.19261 3390.06265 4121.94374 3390.09069 4121.71171 3390.17188 4121.5796 3390.19433 4121.44749 3390.17188 4121.21546 3390.09069 4120.96659 3390.06265 4115.89341 3390.06265 4115.64454 3390.09069 4115.41251 3390.17188 4115.2804 3390.19433 4115.14829 3390.17188 4114.91626 3390.09069 4114.66739 3390.06265 4109.59421 3390.06265 4109.34534 3390.09069 4109.13029 3390.16594 4108.93737 3390.28716 4108.67563 3390.56623 4108.55834 3390.63105 4108.42623 3390.6535 4006.42014 3390.6535 4006.31214 3390.65552 4005.96362 3390.6882 4005.8682 3390.70441 4005.52843 3390.78868 4005.4365 3390.81893 4005.11309 3390.95289 4005.02668 3390.99651 4004.72685 3391.17718 4004.64792 3391.23318 4004.38031 3391.4549 4004.11312 3391.71889 4003.98395 3391.80436 4003.83199 3391.83435 4003.09306 3391.83435 4002.84419 3391.86239 4002.62914 3391.93764 4002.43622 3392.05886 4002.27511 3392.21997 4002.22839 3392.29432 4002.11749 3392.41031 4001.96994 3392.47338 4001.80946 3392.47338 4001.66191 3392.41031 4001.55101 3392.29432 4001.50429 3392.21997 4001.34318 3392.05886 4001.15026 3391.93764 4000.93521 3391.86239 4000.68634 3391.83435 3996.79426 3391.83435 3996.54539 3391.86239 3996.33034 3391.93764 3996.13742 3392.05886 3995.97631 3392.21997 3995.85509 3392.41289 3995.77984 3392.62794 3995.7518 3392.87681 3995.7518 3395.69291 3995.72135 3395.84598 3995.63464 3395.97575 3994.87755 3396.73284 3994.74778 3396.81955 3994.59471 3396.85 3978.27529 3396.85 Z "},{"net":"+3.3V","svgpath":"M 4104.28437 3419.18575 L 4104.1313 3419.2162 3924.2753 3419.2162 3924.12223 3419.18575 3923.99246 3419.09904 3923.90575 3418.96927 3923.8753 3418.8162 3923.8753 3415.51501 3923.84726 3415.26614 3923.77201 3415.05109 3923.65079 3414.85817 3923.48968 3414.69706 3923.29676 3414.57584 3923.08171 3414.50059 3922.83284 3414.47255 3915.00376 3414.47255 3914.75489 3414.50059 3914.53984 3414.57584 3914.34692 3414.69706 3914.18581 3414.85817 3914.06459 3415.05109 3913.98934 3415.26614 3913.9613 3415.51501 3913.9613 3418.8162 3913.93085 3418.96927 3913.84414 3419.09904 3913.71437 3419.18575 3913.5613 3419.2162 3910.1209 3419.2162 3909.96783 3419.18575 3909.83806 3419.09904 3909.75135 3418.96927 3909.7209 3418.8162 3909.7209 3386.2232 3909.75135 3386.07013 3909.83806 3385.94036 3909.96783 3385.85365 3910.1209 3385.8232 3913.5613 3385.8232 3913.71437 3385.85365 3913.84414 3385.94036 3913.93085 3386.07013 3913.9613 3386.2232 3913.9613 3389.48559 3913.98934 3389.73446 3914.06459 3389.94951 3914.18581 3390.14243 3914.34692 3390.30354 3914.53984 3390.42476 3914.75489 3390.50001 3915.00376 3390.52805 3922.83284 3390.52805 3923.08171 3390.50001 3923.29676 3390.42476 3923.48968 3390.30354 3923.65079 3390.14243 3923.77201 3389.94951 3923.84726 3389.73446 3923.8753 3389.48559 3923.8753 3386.2232 3923.90575 3386.07013 3923.99246 3385.94036 3924.12223 3385.85365 3924.2753 3385.8232 3946.55181 3385.8232 3946.70486 3385.85364 3946.83462 3385.94032 3946.92134 3386.07006 3946.95181 3386.22311 3946.92141 3386.37617 3946.83475 3386.50594 3933.48726 3399.86274 3933.35746 3399.94953 3933.20432 3399.98 3932.93415 3399.98 3932.78108 3399.94955 3932.65131 3399.86284 3932.5646 3399.73307 3932.53415 3399.58 3932.53415 3398.98821 3932.50611 3398.73934 3932.47126 3398.5718 3932.50611 3398.40426 3932.53415 3398.13258 3932.20379 3398.13258 3932.09306 3398.11694 3931.95561 3398.04904 3931.74056 3397.97379 3931.49169 3397.94575 3926.42109 3397.94575 3926.15372 3397.97936 3925.81714 3398.11694 3925.70641 3398.13258 3925.37605 3398.13258 3925.40409 3398.40426 3925.43894 3398.5718 3925.40409 3398.73934 3925.37605 3398.98821 3925.37605 3400.12439 3925.40409 3400.37326 3925.43876 3400.5403 3925.40409 3400.70734 3925.37605 3400.95621 3925.37605 3402.09239 3925.40409 3402.34126 3925.43894 3402.5088 3925.40409 3402.67634 3925.37605 3402.92521 3925.37605 3404.06139 3925.40409 3404.31026 3925.43876 3404.4773 3925.40409 3404.64434 3925.37605 3404.89321 3925.37605 3406.02939 3925.40409 3406.27826 3925.43894 3406.4458 3925.40409 3406.61334 3925.37605 3406.86221 3925.37605 3407.99839 3925.40409 3408.24726 3925.43881 3408.41444 3925.40419 3408.58134 3925.37615 3408.83021 3925.37615 3409.96639 3925.40419 3410.21526 3925.47944 3410.43031 3925.60066 3410.62323 3925.76177 3410.78434 3925.95469 3410.90556 3926.16974 3410.98081 3926.41861 3411.00885 3931.49179 3411.00885 3931.74066 3410.98081 3931.95571 3410.90556 3932.14863 3410.78434 3932.30974 3410.62323 3932.43096 3410.43031 3932.50621 3410.21526 3932.53425 3409.96639 3932.53425 3409.34 3932.5647 3409.18693 3932.65141 3409.05716 3932.78118 3408.97045 3932.93425 3408.94 3938.49471 3408.94 3938.64778 3408.97045 3938.77755 3409.05716 3946.6713 3416.95091 3946.91661 3417.15222 3947.17906 3417.29251 3947.46384 3417.3789 3947.77965 3417.41 3965.35035 3417.41 3965.66616 3417.3789 3965.95094 3417.29251 3966.21339 3417.15222 3966.45861 3416.95098 3975.1329 3408.26731 3975.26272 3408.18049 3975.4159 3408.15 3994.59471 3408.15 3994.74778 3408.18045 3994.87755 3408.26716 3995.63464 3409.02425 3995.72135 3409.15402 3995.7518 3409.30709 3995.7518 3412.12319 3995.77984 3412.37206 3995.85509 3412.58711 3995.97631 3412.78003 3996.13742 3412.94114 3996.33034 3413.06236 3996.54539 3413.13761 3996.79426 3413.16565 4000.68634 3413.16565 4000.93521 3413.13761 4001.15026 3413.06236 4001.34318 3412.94114 4001.50429 3412.78003 4001.55101 3412.70568 4001.66191 3412.58969 4001.80946 3412.52662 4001.96994 3412.52662 4002.11749 3412.58969 4002.22839 3412.70568 4002.27511 3412.78003 4002.43622 3412.94114 4002.62914 3413.06236 4002.84419 3413.13761 4003.09306 3413.16565 4006.98514 3413.16565 4007.23401 3413.13761 4007.44906 3413.06236 4007.60294 3412.98413 4007.71368 3412.9685 4010.34637 3412.9685 4010.45088 3412.9666 4010.80299 3412.93377 4010.89489 3412.91821 4011.23817 3412.83326 4011.32672 3412.80418 4011.65351 3412.66902 4011.73673 3412.62706 4012.03973 3412.44471 4012.11577 3412.39082 4012.3882 3412.16534 4012.46352 3412.09286 4016.19892 3408.36536 4016.32858 3408.27887 4016.48146 3408.2485 4108.16874 3408.2485 4108.33211 3408.28339 4108.46699 3408.38196 4108.54985 3408.52703 4108.56622 3408.69329 4108.55175 3408.82171 4108.55175 3409.82877 4110.60127 3409.82877 4110.60127 3408.6485 4110.63172 3408.49543 4110.71843 3408.36566 4110.8482 3408.27895 4111.00127 3408.2485 4113.26033 3408.2485 4113.4134 3408.27895 4113.54317 3408.36566 4113.62988 3408.49543 4113.66032 3408.6485 4113.66032 3409.82877 4116.90048 3409.82877 4116.90048 3408.6485 4116.93092 3408.49543 4117.01763 3408.36566 4117.1474 3408.27895 4117.30048 3408.2485 4118.33671 3408.24639 4118.68263 3408.21382 4118.78061 3408.19713 4119.11782 3408.11337 4119.40631 3408.00178 4119.5594 3407.97127 4119.71251 3408.00168 4119.84232 3408.08839 4119.92907 3408.21817 4119.95953 3408.37127 4119.95953 3409.82877 4123.19967 3409.82877 4123.19967 3407.77925 4122.19261 3407.77925 4121.94374 3407.80729 4121.71171 3407.88848 4121.5796 3407.91093 4121.44749 3407.88848 4121.21546 3407.80729 4120.90239 3407.77925 4120.74936 3407.74882 4120.61962 3407.66217 4120.5329 3407.53247 4120.50239 3407.37945 4120.53274 3407.22641 4120.61933 3407.09662 4126.84222 3400.86416 4126.91538 3400.78815 4127.14027 3400.51651 4127.19474 3400.43968 4127.37663 3400.13755 4127.41904 3400.05345 4127.55388 3399.72759 4127.58328 3399.63811 4127.66804 3399.29579 4127.68378 3399.20294 4127.71657 3398.85181 4127.7185 3398.74634 4127.7185 3397.34558 4127.74095 3397.21346 4127.80577 3397.09618 4127.92263 3396.99624 4128.08374 3396.83513 4128.20496 3396.64221 4128.28021 3396.42716 4128.30825 3396.17829 4128.30825 3393.80785 4128.34011 3393.6514 4128.43062 3393.51989 4128.56536 3393.43424 4128.72286 3393.40811 4128.87803 3393.44567 4129.00616 3393.54092 4129.07044 3393.61426 4129.69189 3394.42081 4130.2528 3395.27056 4130.75019 3396.159 4131.1814 3397.08137 4131.54414 3398.03276 4131.83646 3399.00809 4132.0568 3400.00215 4132.20399 3401.00965 4132.27725 3402.0252 4132.27619 3403.04339 4132.2008 3404.05879 4132.05149 3405.06598 4131.82907 3406.05958 4131.5347 3407.03429 4131.16998 3407.98492 4130.73683 3408.90638 4130.23758 3409.79378 4129.67489 3410.64236 4129.05175 3411.4476 4128.3715 3412.20521 4127.77968 3412.77579 4127.65175 3412.85876 4127.50205 3412.88782 4126.25872 3412.88782 4126.25872 3413.78218 4126.23885 3413.90671 4126.18118 3414.01886 4126.02574 3414.15321 4125.15606 3414.68271 4124.25005 3415.14732 4123.75612 3415.35722 4123.6026 3415.38907 4123.44863 3415.35947 4123.31787 3415.27296 4123.2304 3415.14283 4123.19967 3414.98908 4123.19967 3412.88782 4119.95953 3412.88782 4119.95953 3414.93735 4120.96659 3414.93735 4121.21546 3414.90931 4121.44749 3414.82812 4121.5796 3414.80567 4121.71171 3414.82812 4121.94374 3414.90931 4122.19261 3414.93735 4122.68108 3414.93735 4122.83752 3414.96921 4122.96904 3415.05972 4123.05469 3415.19446 4123.08081 3415.35196 4123.04326 3415.50713 4122.94801 3415.63526 4122.81023 3415.71593 4122.34854 3415.8723 4121.36319 3416.12881 4120.36174 3416.3127 4119.34954 3416.423 4118.32157 3416.4603 4105.5755 3416.4603 4105.31987 3416.49134 4105.10188 3416.57401 4104.89996 3416.71339 4104.78008 3416.83326 4104.67622 3416.97442 4104.57803 3417.1926 4104.5313 3417.48634 4104.5313 3418.8162 4104.50085 3418.96927 4104.41414 3419.09904 4104.28437 3419.18575 Z M 4114.66739 3414.93735 L 4114.91626 3414.90931 4115.14829 3414.82812 4115.2804 3414.80567 4115.41251 3414.82812 4115.64454 3414.90931 4115.89341 3414.93735 4116.90048 3414.93735 4116.90048 3412.88782 4113.66032 3412.88782 4113.66032 3414.93735 4114.66739 3414.93735 Z M 4110.60127 3414.93735 L 4110.60127 3412.88782 4108.55175 3412.88782 4108.55175 3413.89489 4108.57979 3414.14376 4108.65504 3414.35881 4108.77626 3414.55173 4108.93737 3414.71284 4109.13029 3414.83406 4109.34534 3414.90931 4109.59421 3414.93735 4110.60127 3414.93735 Z M 4126.25872 3407.77925 L 4126.25872 3409.82877 4128.30825 3409.82877 4128.30825 3408.82171 4128.28021 3408.57284 4128.20496 3408.35779 4128.08374 3408.16487 4127.92263 3408.00376 4127.72971 3407.88254 4127.51466 3407.80729 4127.26579 3407.77925 4126.25872 3407.77925 Z M 3932.53425 3395.07402 L 3932.50621 3394.80234 3932.43096 3394.58729 3932.30974 3394.39437 3932.14863 3394.23326 3931.95571 3394.11204 3931.74066 3394.03679 3931.49179 3394.00875 3930.48472 3394.00875 3930.48472 3395.07402 3932.53425 3395.07402 Z M 3925.37615 3395.05121 L 3925.37615 3395.07402 3927.42567 3395.07402 3927.42567 3394.00875 3926.41861 3394.00875 3926.16974 3394.03679 3925.95469 3394.11204 3925.76177 3394.23326 3925.60066 3394.39437 3925.47944 3394.58729 3925.40419 3394.80234 3925.37615 3395.05121 Z M 3925.37605 3397.01921 L 3925.37605 3397.04203 3927.42558 3397.04203 3927.42558 3396.16457 3925.37615 3396.16457 3925.40419 3396.43626 3925.43881 3396.60316 3925.40409 3396.77034 3925.37605 3397.01921 Z M 3932.53415 3397.04203 L 3932.50611 3396.77034 3932.47149 3396.60344 3932.50621 3396.43626 3932.53425 3396.16457 3930.48472 3396.16457 3930.48472 3397.04203 3932.53415 3397.04203 Z "}],"B":[{"net":"+3.3V","svgpath":"M 3910.1209 3419.2162 L 3909.96783 3419.18575 3909.83806 3419.09904 3909.75135 3418.96927 3909.7209 3418.8162 3909.7209 3386.2232 3909.75135 3386.07013 3909.83806 3385.94036 3909.96783 3385.85365 3910.1209 3385.8232 4104.1313 3385.8232 4104.28437 3385.85365 4104.41414 3385.94036 4104.50085 3386.07013 4104.5313 3386.2232 4104.5313 3387.5349 4104.56234 3387.79053 4104.64501 3388.00852 4104.78439 3388.21044 4104.90426 3388.33032 4105.04542 3388.43418 4105.2636 3388.53237 4105.55734 3388.5791 4118.32157 3388.5791 4119.37868 3388.61853 4120.39065 3388.73096 4121.39171 3388.91695 4122.37652 3389.17551 4123.33984 3389.50528 4124.27651 3389.90448 4125.18154 3390.37099 4126.05011 3390.90232 4126.87757 3391.49563 4127.65952 3392.14776 4128.39178 3392.85523 4129.07044 3393.61426 4129.69189 3394.42081 4130.2528 3395.27056 4130.75019 3396.159 4131.1814 3397.08137 4131.54414 3398.03276 4131.83646 3399.00809 4132.0568 3400.00215 4132.20399 3401.00965 4132.27725 3402.0252 4132.27619 3403.04339 4132.2008 3404.05879 4132.05149 3405.06598 4131.82907 3406.05958 4131.5347 3407.03429 4131.16998 3407.98492 4130.73683 3408.90638 4130.23758 3409.79378 4129.67489 3410.64236 4129.05175 3411.4476 4128.3715 3412.20521 4127.63776 3412.91114 4126.85444 3413.56163 4126.02574 3414.15321 4125.15606 3414.68271 4124.25005 3415.14732 4123.31255 3415.54456 4122.34854 3415.8723 4121.36319 3416.12881 4120.36174 3416.3127 4119.34954 3416.423 4118.32157 3416.4603 4105.5755 3416.4603 4105.31987 3416.49134 4105.10188 3416.57401 4104.89996 3416.71339 4104.78008 3416.83326 4104.67622 3416.97442 4104.57803 3417.1926 4104.5313 3417.48634 4104.5313 3418.8162 4104.50085 3418.96927 4104.41414 3419.09904 4104.28437 3419.18575 4104.1313 3419.2162 3910.1209 3419.2162 Z M 3955.71752 3413.37977 L 3956.62704 3413.36071 3957.53178 3413.26562 3958.42538 3413.09516 3959.30159 3412.85052 3960.15425 3412.53341 3960.97739 3412.14608 3961.76523 3411.69122 3962.51225 3411.17203 3963.2132 3410.59215 3963.86317 3409.95565 3964.45759 3409.267 3964.99231 3408.53102 3965.46357 3407.75288 3965.86807 3406.93803 3966.20296 3406.0922 3966.46589 3405.22131 3966.65504 3404.33147 3966.76905 3403.42892 3966.80715 3402.52 3966.76905 3401.61108 3966.65504 3400.70853 3966.46589 3399.81869 3966.20296 3398.9478 3965.86807 3398.10197 3965.46357 3397.28712 3964.99231 3396.50898 3964.45759 3395.773 3963.86317 3395.08435 3963.2132 3394.44785 3962.51225 3393.86797 3961.76523 3393.34878 3960.97739 3392.89392 3960.15425 3392.50659 3959.30159 3392.18948 3958.42538 3391.94484 3957.41788 3391.75481 3956.50904 3391.65929 3955.59952 3391.64023 3954.6916 3391.69736 3953.79164 3391.83025 3952.90595 3392.03798 3952.04076 3392.3191 3951.20212 3392.67163 3950.39593 3393.0931 3949.62782 3393.58056 3948.90321 3394.13057 3948.22715 3394.73929 3947.60441 3395.40245 3947.03934 3396.11539 3946.53591 3396.87311 3946.09765 3397.67031 3945.72763 3398.50138 3945.42845 3399.36049 3945.20222 3400.24163 3945.0505 3401.13861 3944.97438 3402.04514 3944.97438 3402.95486 3945.0505 3403.86139 3945.20222 3404.75837 3945.42845 3405.63951 3945.72763 3406.49862 3946.09765 3407.32969 3946.53591 3408.12689 3947.03934 3408.88461 3947.60441 3409.59755 3948.22715 3410.26071 3948.90321 3410.86943 3949.62782 3411.41944 3950.39593 3411.9069 3951.20212 3412.32837 3952.04076 3412.6809 3952.90595 3412.96202 3953.79579 3413.17072 3953.89726 3413.18792 3954.8096 3413.32264 3955.71752 3413.37977 Z "}]},"pads":[],"nets":["+3.3V","ARM_B","ARM_G","ARM_R","LED1_3","R2_1","LED1_1"],"bom":{"both":[[1,"THD0515-08CL-SN","FPC-SMD_8P-P0.50_THD0515-08CL-SN",[["ARM",0]],["THD","THD0515-08CL-SN","LCSC","C283141"]],[2,"15","R0603",[["R1",1],["R2",2]],["YAGEO","RC0603FR-0715RL","LCSC","C128059"]],[1,"66.5","R0603",[["R3",3]],["UniOhm","0603WAF665JT5E","LCSC","C23226"]],[1,"LTST-K563BGEW","LTST-K563BGEW",[["LED1",4]],["Lite-On","LTST-K563BGEW","LCSC","C284911"]]],"F":[[1,"THD0515-08CL-SN","FPC-SMD_8P-P0.50_THD0515-08CL-SN",[["ARM",0]],["THD","THD0515-08CL-SN","LCSC","C283141"]],[2,"15","R0603",[["R1",1],["R2",2]],["YAGEO","RC0603FR-0715RL","LCSC","C128059"]],[1,"66.5","R0603",[["R3",3]],["UniOhm","0603WAF665JT5E","LCSC","C23226"]],[1,"LTST-K563BGEW","LTST-K563BGEW",[["LED1",4]],["Lite-On","LTST-K563BGEW","LCSC","C284911"]]],"B":[],"skipped":[],"customColumns":["BOM_Manufacturer","BOM_Manufacturer Part","BOM_Supplier","BOM_Supplier Part"]}};
///////////////////////////////////////////////

///////////////////////////////////////////////
/* Utility functions */

var storagePrefix = 'ibom__' + pcbdata.metadata.title + '__' +
  pcbdata.metadata.revision + '__#';

function buildKeyForLocalStorageDict() {  
  return storagePrefix;
}

var storage;
var _isLocalStorageUsed = false;

function initStorage(key) {
  try {
    window.localStorage.getItem("blank");
    storage = window.localStorage;    
    _isLocalStorageUsed = true;
  } catch (e) {
    // localStorage not available
  }
  if (!storage) {
    try {
      window.sessionStorage.getItem("blank");
      storage = window.sessionStorage;      
    } catch (e) {
      // sessionStorage also not available
    }
  }
}



function readStorage(key) {
  if(!storage) {
    return null;
  }

  if(!_isLocalStorageUsed) {    
    return storage.getItem(storagePrefix + key);
  }

  try {
    var dictKey = buildKeyForLocalStorageDict();
    var obj = JSON.parse(storage.getItem(dictKey));    
    if(!obj || !obj.hasOwnProperty(key)) {
      return null;
    }

    return JSON.parse(storage.getItem(dictKey))[key];
  } catch(e) {
    console.log('[ibom]: Unable to obtain a value from local storage!');
    console.log(e);
  }
  
  // Original implementation
  // return storage.getItem(storagePrefix + key);
}


function writeStorage(key, value) {
  if(!storage) {
    return;
  }

  if(!_isLocalStorageUsed) {
    storage.setItem(storagePrefix + key, value);
    return;
  }

  try {
    var dictKey = buildKeyForLocalStorageDict();
    if(!storage.getItem(dictKey)) {
      storage.setItem(dictKey,JSON.stringify({
        _storagePrefix: storagePrefix
      }));
    }

    var obj = JSON.parse(storage.getItem(dictKey));
    obj[key] = value;
    storage.setItem(dictKey,JSON.stringify(obj));            
  } catch(e) {
    console.log('[ibom]: Unable to write key to local storage!');
    console.log(e);
  }
  
  // Original implementation
  // storage.setItem(storagePrefix + key, value);
}

function fancyDblClickHandler(el, onsingle, ondouble) {
  return function() {
    if (el.getAttribute("data-dblclick") == null) {
      el.setAttribute("data-dblclick", 1);
      setTimeout(function() {
        if (el.getAttribute("data-dblclick") == 1) {
          onsingle();
        }
        el.removeAttribute("data-dblclick");
      }, 200);
    } else {
      el.removeAttribute("data-dblclick");
      ondouble();
    }
  }
}

function smoothScrollToRow(rowid) {
  document.getElementById(rowid).scrollIntoView({
    behavior: "smooth",
    block: "center",
    inline: "nearest"
  });
}

function focusInputField(input) {
  input.scrollIntoView(false);
  input.focus();
  input.select();
}

function copyToClipboard() {
  var text = '';
  for (var node of bomhead.childNodes[0].childNodes) {
    if (node.firstChild) {
      text = text + node.firstChild.nodeValue;
    }
    if (node != bomhead.childNodes[0].lastChild) {
      text += '\t';
    }
  }
  text += '\n';
  for (var row of bombody.childNodes) {
    for (var cell of row.childNodes) {
      for (var node of cell.childNodes) {
        if (node.nodeName == "INPUT") {
          if (node.checked) {
            text = text + '✓';
          }
        } else if (node.nodeName == "MARK") {
          text = text + node.firstChild.nodeValue;
        } else {
          text = text + node.nodeValue;
        }
      }
      if (cell != row.lastChild) {
        text += '\t';
      }
    }
    text += '\n';
  }
  var textArea = document.createElement("textarea");
  textArea.classList.add('clipboard-temp');
  textArea.value = text;

  document.body.appendChild(textArea);
  textArea.focus();
  textArea.select();

  try {
    if (document.execCommand('copy')) {
      console.log('Bom copied to clipboard.');
    }
  } catch (err) {
    console.log('Can not copy to clipboard.');
  }

  document.body.removeChild(textArea);
}

function removeGutterNode(node) {
  for (var i = 0; i < node.childNodes.length; i++) {
    if (node.childNodes[i].classList &&
      node.childNodes[i].classList.contains("gutter")) {
      node.removeChild(node.childNodes[i]);
      break;
    }
  }
}

function cleanGutters() {
  removeGutterNode(document.getElementById("bot"));
  removeGutterNode(document.getElementById("canvasdiv"));
}

var units = {
  prefixes: {
    giga: ["G", "g", "giga", "Giga", "GIGA"],
    mega: ["M", "mega", "Mega", "MEGA"],
    kilo: ["K", "k", "kilo", "Kilo", "KILO"],
    milli: ["m", "milli", "Milli", "MILLI"],
    micro: ["U", "u", "micro", "Micro", "MICRO", "μ", "µ"], // different utf8 μ
    nano: ["N", "n", "nano", "Nano", "NANO"],
    pico: ["P", "p", "pico", "Pico", "PICO"],
  },
  unitsShort: ["R", "r", "Ω", "F", "f", "H", "h"],
  unitsLong: [
    "OHM", "Ohm", "ohm", "ohms",
    "FARAD", "Farad", "farad",
    "HENRY", "Henry", "henry"
  ],
  getMultiplier: function(s) {
    if (this.prefixes.giga.includes(s)) return 1e9;
    if (this.prefixes.mega.includes(s)) return 1e6;
    if (this.prefixes.kilo.includes(s)) return 1e3;
    if (this.prefixes.milli.includes(s)) return 1e-3;
    if (this.prefixes.micro.includes(s)) return 1e-6;
    if (this.prefixes.nano.includes(s)) return 1e-9;
    if (this.prefixes.pico.includes(s)) return 1e-12;
    return 1;
  },
  valueRegex: null,
}

function initUtils() {
  var allPrefixes = units.prefixes.giga
                    .concat(units.prefixes.mega)
                    .concat(units.prefixes.kilo)
                    .concat(units.prefixes.milli)
                    .concat(units.prefixes.micro)
                    .concat(units.prefixes.nano)
                    .concat(units.prefixes.pico);
  var allUnits = units.unitsShort.concat(units.unitsLong);
  units.valueRegex = new RegExp("^([0-9\.]+)" +
                         "\\s*(" + allPrefixes.join("|") + ")?" +
                         "(" + allUnits.join("|") + ")?" +
                         "(\\b.*)?$", "");
  units.valueAltRegex = new RegExp("^([0-9]*)" +
                         "(" + units.unitsShort.join("|") + ")?" +
                         "([GgMmKkUuNnPp])?" +
                         "([0-9]*)" +
                         "(\\b.*)?$", "");
  for (var bom_type of ["both", "F", "B"]) {
    for (var row of pcbdata.bom[bom_type]) {
      row.push(parseValue(row[1], row[3][0][0]));
    }
  }
}

function parseValue(val, ref) {
  var inferUnit = (unit, ref) => {
    if (unit) {
      unit = unit.toLowerCase();
      if (unit == 'Ω' || unit == "ohm" || unit == "ohms") {
        unit = 'r';
      }
      unit = unit[0];
    } else {
      ref = /^([a-z]+)\d+$/i.exec(ref);
      if (ref) {
        ref = ref[1].toLowerCase();
        if (ref == "c") unit = 'f';
        else if (ref == "l") unit = 'h';
        else if (ref == "r" || ref == "rv") unit = 'r';
        else unit = null;
      }
    }
    return unit;
  };
  val = val.replace(/,/g, "");
  var match = units.valueRegex.exec(val);
  var unit;
  if (match) {
    val = parseFloat(match[1]);
    if (match[2]) {
      val = val * units.getMultiplier(match[2]);
    }
    unit = inferUnit(match[3], ref);
    if (!unit) return null;
    else return {
      val: val,
      unit: unit,
      extra: match[4],
    }
  }
  match = units.valueAltRegex.exec(val);
  if (match && (match[1] || match[4])) {
    val = parseFloat(match[1] + "." + match[4]);
    if (match[3]) {
      val = val * units.getMultiplier(match[3]);
    }
    unit = inferUnit(match[2], ref);
    if (!unit) return null;
    else return {
      val: val,
      unit: unit,
      extra: match[5],
    }
  }
  return null;
}

function valueCompare(a, b, stra, strb) {
  if (a === null && b === null) {
    // Failed to parse both values, compare them as strings.
    if (stra != strb) return stra > strb ? 1 : -1;
    else return 0;
  } else if (a === null) {
    return 1;
  } else if (b === null) {
    return -1;
  } else {
    if (a.unit != b.unit) return a.unit > b.unit ? 1 : -1;
    else if (a.val != b.val) return a.val > b.val ? 1 : -1;
    else if (a.extra != b.extra) return a.extra > b.extra ? 1 : -1;
    else return 0;
  }
}

function validateSaveImgDimension(element) {
  var valid = false;
  var intValue = 0;
  if (/^[1-9]\d*$/.test(element.value)) {
    intValue = parseInt(element.value);
    if (intValue <= 16000) {
      valid = true;
    }
  }
  if (valid) {
    element.classList.remove("invalid");
  } else {
    element.classList.add("invalid");
  }
  return intValue;
}

function saveImage(layer) {
  var width = validateSaveImgDimension(document.getElementById("render-save-width"));
  var height = validateSaveImgDimension(document.getElementById("render-save-height"));
  var bgcolor = null;
  if (!document.getElementById("render-save-transparent").checked) {
    var style = getComputedStyle(topmostdiv);
    bgcolor = style.getPropertyValue("background-color");
  }
  if (!width || !height) return;

  // Prepare image
  var canvas = document.createElement("canvas");
  var layerdict = {
    transform: {
      x: 0,
      y: 0,
      s: 1,
      panx: 0,
      pany: 0,
      zoom: 1,
    },
    bg: canvas,
    fab: canvas,
    silk: canvas,
    highlight: canvas,
    layer: layer,
  }
  // Do the rendering
  recalcLayerScale(layerdict, width, height);
  prepareLayer(layerdict);
  clearCanvas(canvas, bgcolor);
  drawBackground(layerdict, false);
  drawHighlightsOnLayer(layerdict, false);

  // Save image
  var imgdata = canvas.toDataURL("image/png");

  var filename = pcbdata.metadata.title;
  if (pcbdata.metadata.revision) {
    filename += `.${pcbdata.metadata.revision}`;
  }
  filename += `.${layer}.png`;
  saveFile(filename, dataURLtoBlob(imgdata));
}

function saveSettings() {
  var data = {
    type: "InteractiveHtmlBom settings",
    version: 1,
    pcbmetadata: pcbdata.metadata,
    settings: settings,
  }
  var blob = new Blob([JSON.stringify(data, null, 4)], {type: "application/json"});
  saveFile(`${pcbdata.metadata.title}.settings.json`, blob);
}

function loadSettings() {
  var input = document.createElement("input");
  input.type = "file";
  input.accept = ".settings.json";
  input.onchange = function(e) {
    var file = e.target.files[0];
    var reader = new FileReader();
    reader.onload = readerEvent => {
      var content = readerEvent.target.result;
      var newSettings;
      try {
        newSettings = JSON.parse(content);
      } catch(e) {
        alert("Selected file is not InteractiveHtmlBom settings file.");
        return;
      }
      if (newSettings.type != "InteractiveHtmlBom settings") {
        alert("Selected file is not InteractiveHtmlBom settings file.");
        return;
      }
      var metadataMatches = newSettings.hasOwnProperty("pcbmetadata");
      if (metadataMatches) {
        for (var k in pcbdata.metadata) {
          if (!newSettings.pcbmetadata.hasOwnProperty(k) || newSettings.pcbmetadata[k] != pcbdata.metadata[k]) {
            metadataMatches = false;
          }
        }
      }
      if (!metadataMatches) {
        var currentMetadata = JSON.stringify(pcbdata.metadata, null, 4);
        var fileMetadata = JSON.stringify(newSettings.pcbmetadata, null, 4);
        if (!confirm(
          `Settins file metadata does not match current metadata.\n\n` +
          `Page metadata:\n${currentMetadata}\n\n` +
          `Settings file metadata:\n${fileMetadata}\n\n` +
          `Press OK if you would like to import settings anyway.`)) {
          return;
        }
      }
      overwriteSettings(newSettings.settings);
    }
    reader.readAsText(file, 'UTF-8');
  }
  input.click();
}

function overwriteSettings(newSettings) {

  initDone = false;
  Object.assign(settings, newSettings);
  writeStorage("bomlayout", settings.bomlayout);
  writeStorage("bommode", settings.bommode);
  writeStorage("canvaslayout", settings.canvaslayout);
  writeStorage("bomCheckboxes", settings.checkboxes.join(","));
  document.getElementById("bomCheckboxes").value = settings.checkboxes.join(",");
  for (var checkbox of settings.checkboxes) {
    writeStorage("checkbox_" + checkbox, settings.checkboxStoredRefs[checkbox]);
  }
  writeStorage("darkenWhenChecked", settings.darkenWhenChecked);
  padsVisible(settings.renderPads);
  document.getElementById("padsCheckbox").checked = settings.renderPads;
  fabricationVisible(settings.renderFabrication);
  document.getElementById("fabricationCheckbox").checked = settings.renderFabrication;
  silkscreenVisible(settings.renderSilkscreen);
  document.getElementById("silkscreenCheckbox").checked = settings.renderSilkscreen;
  referencesVisible(settings.renderReferences);
  document.getElementById("referencesCheckbox").checked = settings.renderReferences;
  valuesVisible(settings.renderValues);
  document.getElementById("valuesCheckbox").checked = settings.renderValues;
  tracksVisible(settings.renderTracks);
  document.getElementById("tracksCheckbox").checked = settings.renderTracks;
  zonesVisible(settings.renderZones);
  document.getElementById("zonesCheckbox").checked = settings.renderZones;
  dnpOutline(settings.renderDnpOutline);
  document.getElementById("dnpOutlineCheckbox").checked = settings.renderDnpOutline;
  setRedrawOnDrag(settings.redrawOnDrag);
  document.getElementById("dragCheckbox").checked = settings.redrawOnDrag;
  setShowCrosshair(settings.showCrosshair);
  document.getElementById("crosshairCheckbox").checked = settings.showCrosshair;
  setDarkMode(settings.darkMode);
  document.getElementById("darkmodeCheckbox").checked = settings.darkMode;
  setHighlightPin1(settings.highlightpin1);
  document.getElementById("highlightpin1Checkbox").checked = settings.highlightpin1;
  writeStorage("boardRotation", settings.boardRotation);
  document.getElementById("boardRotation").value = settings.boardRotation / 5;
  document.getElementById("rotationDegree").textContent = settings.boardRotation;
  initDone = true;
  prepCheckboxes();
  changeBomLayout(settings.bomlayout);
}

function saveFile(filename, blob) {
  var link = document.createElement("a");
  var objurl = URL.createObjectURL(blob);
  link.download = filename;
  link.href = objurl;
  link.click();
}

function dataURLtoBlob(dataurl) {
  var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
      bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
  while(n--){
      u8arr[n] = bstr.charCodeAt(n);
  }
  return new Blob([u8arr], {type:mime});
}

var settings = {
  canvaslayout: "default",
  bomlayout: "default",
  bommode: "grouped",
  checkboxes: [],
  checkboxStoredRefs: {},
  darkMode: false,
  highlightpin1: false,
  redrawOnDrag: true,
  boardRotation: 0,
  renderPads: true,
  renderReferences: true,
  renderValues: true,
  renderSilkscreen: true,
  renderFabrication: true,
  renderDnpOutline: false,
  renderTracks: true,
  renderZones: true,
}

function initDefaults() {
  settings.bomlayout = readStorage("bomlayout");
  if (settings.bomlayout === null) {
    settings.bomlayout = config.bom_view;
  }
  if (!['bom-only', 'left-right', 'top-bottom'].includes(settings.bomlayout)) {
    settings.bomlayout = config.bom_view;
  }
  settings.bommode = readStorage("bommode");
  if (settings.bommode === null) {
    settings.bommode = "grouped";
  }
  if (!["grouped", "ungrouped", "netlist"].includes(settings.bommode)) {
    settings.bommode = "grouped";
  }
  settings.canvaslayout = readStorage("canvaslayout");
  if (settings.canvaslayout === null) {
    settings.canvaslayout = config.layer_view;
  }
  var bomCheckboxes = readStorage("bomCheckboxes");
  if (bomCheckboxes === null) {
    bomCheckboxes = config.checkboxes;
  }
  settings.checkboxes = bomCheckboxes.split(",").filter((e) => e);
  document.getElementById("bomCheckboxes").value = bomCheckboxes;

  settings.darkenWhenChecked = readStorage("darkenWhenChecked") || "";
  populateDarkenWhenCheckedOptions();

  function initBooleanSetting(storageString, def, elementId, func) {    
    var b = readStorage(storageString);
    if (b === null) {
      b = def;
    } else {
      b = (b === true);
    }
    document.getElementById(elementId).checked = b;
    func(b);
  }

  initBooleanSetting("padsVisible", config.show_pads, "padsCheckbox", padsVisible);
  initBooleanSetting("fabricationVisible", config.show_fabrication, "fabricationCheckbox", fabricationVisible);
  initBooleanSetting("silkscreenVisible", config.show_silkscreen, "silkscreenCheckbox", silkscreenVisible);
  initBooleanSetting("referencesVisible", true, "referencesCheckbox", referencesVisible);
  initBooleanSetting("valuesVisible", false, "valuesCheckbox", valuesVisible);
  if ("tracks" in pcbdata) {
    initBooleanSetting("tracksVisible", true, "tracksCheckbox", tracksVisible);
    initBooleanSetting("zonesVisible", true, "zonesCheckbox", zonesVisible);
  } else {
    document.getElementById("tracksAndZonesCheckboxes").style.display = "none";
    tracksVisible(false);
    zonesVisible(false);
  }
  initBooleanSetting("dnpOutline", false, "dnpOutlineCheckbox", dnpOutline);
  initBooleanSetting("redrawOnDrag", config.redraw_on_drag, "dragCheckbox", setRedrawOnDrag);
  initBooleanSetting("showCrosshair", config.show_crosshair, "crosshairCheckbox", setShowCrosshair);
  initBooleanSetting("darkmode", config.dark_mode, "darkmodeCheckbox", setDarkMode);
  initBooleanSetting("highlightpin1", config.highlight_pin1, "highlightpin1Checkbox", setHighlightPin1);
  settings.boardRotation = readStorage("boardRotation");
  if (settings.boardRotation === null) {
    settings.boardRotation = config.board_rotation * 5;
  } else {
    settings.boardRotation = parseInt(settings.boardRotation);
  }
  document.getElementById("boardRotation").value = settings.boardRotation / 5;
  document.getElementById("rotationDegree").textContent = settings.boardRotation;
}

// Helper classes for user js callbacks.

const IBOM_EVENT_TYPES = {
  ALL: "all",
  HIGHLIGHT_EVENT: "highlightEvent",
  CHECKBOX_CHANGE_EVENT: "checkboxChangeEvent",
  BOM_BODY_CHANGE_EVENT: "bomBodyChangeEvent",
}

const EventHandler = {
  callbacks: {},
  init: function() {
    for (eventType of Object.values(IBOM_EVENT_TYPES))
      this.callbacks[eventType] = [];
  },
  registerCallback: function(eventType, callback) {
    this.callbacks[eventType].push(callback);
  },
  emitEvent: function(eventType, eventArgs) {
    event = {
      eventType: eventType,
      args: eventArgs,
    }
    var callback;
    for(callback of this.callbacks[eventType])
      callback(event);
    for(callback of this.callbacks[IBOM_EVENT_TYPES.ALL])
      callback(event);
  }
}
EventHandler.init();

///////////////////////////////////////////////

///////////////////////////////////////////////
/* PCB rendering code */

var emptyContext2d = document.createElement("canvas").getContext("2d");
var hitTestContext2d = document.createElement("canvas").getContext("2d");

function deg2rad(deg) {
  return deg * Math.PI / 180;
}

function calcFontPoint(linepoint, text, offsetx, offsety, tilt) {
  var point = [
    linepoint[0] * text.width + offsetx,
    linepoint[1] * text.height + offsety
  ];
  // This approximates pcbnew behavior with how text tilts depending on horizontal justification
  point[0] -= (linepoint[1] + 0.5 * (1 + text.justify[0])) * text.height * tilt;
  return point;
}

function drawText(ctx, text, color) {
  if ("ref" in text && !settings.renderReferences) return;
  if ("val" in text && !settings.renderValues) return;
  ctx.save();
  ctx.fillStyle = color;
  ctx.strokeStyle = color;
  ctx.lineCap = "round";
  ctx.lineJoin = "round";
  ctx.lineWidth = text.thickness;

  if ("svgpath" in text) {    
    // TODO: This path must be cached!
    const path = new Path2D(text.svgpath);
    if(text.useTrueTypeFontRendering) {            
      ctx.fill(path);
    } else {
      ctx.stroke(path);
    }
    
    ctx.restore();
    return;
  }

  ctx.translate(...text.pos);
  ctx.translate(text.thickness * 0.5, 0);
  var angle = -text.angle;
  if (text.attr.includes("mirrored")) {
    ctx.scale(-1, 1);
    angle = -angle;
  }
  var tilt = 0;
  if (text.attr.includes("italic")) {
    tilt = 0.125;
  }
  var interline = text.height * 1.5 + text.thickness;
  var txt = text.text.split("\n");
  // KiCad ignores last empty line.
  if (txt[txt.length - 1] == '') txt.pop();
  ctx.rotate(deg2rad(angle));
  var offsety = (1 - text.justify[1]) / 2 * text.height; // One line offset
  offsety -= (txt.length - 1) * (text.justify[1] + 1) / 2 * interline; // Multiline offset
  for (var i in txt) {
    var lineWidth = text.thickness + interline / 2 * tilt;
    for (var j = 0; j < txt[i].length; j++) {
      if (txt[i][j] == '\t') {
        var fourSpaces = 4 * pcbdata.font_data[' '].w * text.width;
        lineWidth += fourSpaces - lineWidth % fourSpaces;
      } else {
        if (txt[i][j] == '~') {
          j++;
          if (j == txt[i].length)
            break;
        }
        lineWidth += pcbdata.font_data[txt[i][j]].w * text.width;
      }
    }
    var offsetx = -lineWidth * (text.justify[0] + 1) / 2;
    var inOverbar = false;
    for (var j = 0; j < txt[i].length; j++) {
      if (txt[i][j] == '\t') {
        var fourSpaces = 4 * pcbdata.font_data[' '].w * text.width;
        offsetx += fourSpaces - offsetx % fourSpaces;
        continue;
      } else if (txt[i][j] == '~') {
        j++;
        if (j == txt[i].length)
          break;
        if (txt[i][j] != '~') {
          inOverbar = !inOverbar;
        }
      }
      var glyph = pcbdata.font_data[txt[i][j]];
      if (inOverbar) {
        var overbarStart = [offsetx, -text.height * 1.4 + offsety];
        var overbarEnd = [offsetx + text.width * glyph.w, overbarStart[1]];

        if (!lastHadOverbar) {
          overbarStart[0] += text.height * 1.4 * tilt;
          lastHadOverbar = true;
        }
        ctx.beginPath();
        ctx.moveTo(...overbarStart);
        ctx.lineTo(...overbarEnd);
        ctx.stroke();
      } else {
        lastHadOverbar = false;
      }
      for (var line of glyph.l) {
        ctx.beginPath();
        ctx.moveTo(...calcFontPoint(line[0], text, offsetx, offsety, tilt));
        for (var k = 1; k < line.length; k++) {
          ctx.lineTo(...calcFontPoint(line[k], text, offsetx, offsety, tilt));
        }
        ctx.stroke();
      }
      offsetx += glyph.w * text.width;
    }
    offsety += interline;
  }
  ctx.restore();
}

function drawedge(ctx, scalefactor, edge, color) {
  ctx.strokeStyle = color;
  ctx.lineWidth = Math.max(1 / scalefactor, edge.width);
  ctx.lineCap = "round";
  if ("svgpath" in edge) {
    ctx.stroke(new Path2D(edge.svgpath));
  } else {
    ctx.beginPath();
    if (edge.type == "segment") {
      ctx.moveTo(...edge.start);
      ctx.lineTo(...edge.end);
    }
    if (edge.type == "rect") {
      ctx.moveTo(...edge.start);
      ctx.lineTo(edge.start[0], edge.end[1]);
      ctx.lineTo(...edge.end);
      ctx.lineTo(edge.end[0], edge.start[1]);
      ctx.lineTo(...edge.start);
    }
    if (edge.type == "arc") {
      ctx.arc(
        ...edge.start,
        edge.radius,
        deg2rad(edge.startangle),
        deg2rad(edge.endangle));
    }
    if (edge.type == "circle") {
      ctx.arc(
        ...edge.start,
        edge.radius,
        0, 2 * Math.PI);
      ctx.closePath();
    }
    if (edge.type == "curve") {
      ctx.moveTo(...edge.start);
      ctx.bezierCurveTo(...edge.cpa, ...edge.cpb, ...edge.end);
    }
    ctx.stroke();
  }
}

function getChamferedRectPath(size, radius, chamfpos, chamfratio) {
  // chamfpos is a bitmask, left = 1, right = 2, bottom left = 4, bottom right = 8
  var path = new Path2D();
  var width = size[0];
  var height = size[1];
  var x = width * -0.5;
  var y = height * -0.5;
  var chamfOffset = Math.min(width, height) * chamfratio;
  path.moveTo(x, 0);
  if (chamfpos & 4) {
    path.lineTo(x, y + height - chamfOffset);
    path.lineTo(x + chamfOffset, y + height);
    path.lineTo(0, y + height);
  } else {
    path.arcTo(x, y + height, x + width, y + height, radius);
  }
  if (chamfpos & 8) {
    path.lineTo(x + width - chamfOffset, y + height);
    path.lineTo(x + width, y + height - chamfOffset);
    path.lineTo(x + width, 0);
  } else {
    path.arcTo(x + width, y + height, x + width, y, radius);
  }
  if (chamfpos & 2) {
    path.lineTo(x + width, y + chamfOffset);
    path.lineTo(x + width - chamfOffset, y);
    path.lineTo(0, y);
  } else {
    path.arcTo(x + width, y, x, y, radius);
  }
  if (chamfpos & 1) {
    path.lineTo(x + chamfOffset, y);
    path.lineTo(x, y + chamfOffset);
    path.lineTo(x, 0);
  } else {
    path.arcTo(x, y, x, y + height, radius);
  }
  path.closePath();
  return path;
}

function getOblongPath(size) {
  return getChamferedRectPath(size, Math.min(size[0], size[1]) / 2, 0, 0);
}

function getPolygonsPath(shape) {
  if (shape.path2d) {
    return shape.path2d;
  }
  if ("svgpath" in shape) {
    shape.path2d = new Path2D(shape.svgpath);
  } else {
    var path = new Path2D();
    for (var polygon of shape.polygons) {
      path.moveTo(...polygon[0]);
      for (var i = 1; i < polygon.length; i++) {
        path.lineTo(...polygon[i]);
      }
      path.closePath();
    }
    shape.path2d = path;
  }
  return shape.path2d;
}

function drawPolygonShape(ctx, shape, color) {
  ctx.save();
  ctx.fillStyle = color;
  if (!("svgpath" in shape)) {
    ctx.translate(...shape.pos);
    ctx.rotate(deg2rad(-shape.angle));
  }
  ctx.fill(getPolygonsPath(shape));
  ctx.restore();
}


function drawPolylineShape(ctx, shape, color) {
  ctx.save();
  ctx.strokeStyle = color;
  ctx.lineWidth = shape.width;
  if (!("svgpath" in shape)) {
    ctx.translate(...shape.pos);
    ctx.rotate(deg2rad(-shape.angle));
  }
  ctx.stroke(getPolygonsPath(shape));
  ctx.restore();
}

function drawDrawing(ctx, scalefactor, drawing, color) {  
  if (["segment", "arc", "circle", "curve"].includes(drawing.type)) {
    drawedge(ctx, scalefactor, drawing, color);
  } else if (drawing.type == "polygon") {
    drawPolygonShape(ctx, drawing, color);
  } else if (drawing.type == "text") {    
    drawText(ctx, drawing, color);
  } else if (drawing.type == "polyline") {    
    drawPolylineShape(ctx, drawing, color);
  }
}

function getCirclePath(radius) {
  var path = new Path2D();
  path.arc(0, 0, radius, 0, 2 * Math.PI);
  path.closePath();
  return path;
}

function getCachedPadPath(pad) {
  if (!pad.path2d) {
    // if path2d is not set, build one and cache it on pad object
    if (pad.shape == "rect") {
      pad.path2d = new Path2D();
      pad.path2d.rect(...pad.size.map(c => -c * 0.5), ...pad.size);
    } else if (pad.shape == "oval") {
      pad.path2d = getOblongPath(pad.size);
    } else if (pad.shape == "circle") {
      pad.path2d = getCirclePath(pad.size[0] / 2);
    } else if (pad.shape == "roundrect") {
      pad.path2d = getChamferedRectPath(pad.size, pad.radius, 0, 0);
    } else if (pad.shape == "chamfrect") {
      pad.path2d = getChamferedRectPath(pad.size, pad.radius, pad.chamfpos, pad.chamfratio)
    } else if (pad.shape == "custom") {
      pad.path2d = getPolygonsPath(pad);
    } else if(pad.shape === "polygon") {      
      pad.path2d = new Path2D();
      if(pad.polygon.length > 1) {
        const pos = {
          x: pad.pos[0],
          y: pad.pos[1]
        };

        pad.path2d.moveTo(pad.polygon[0].x - pos.x, pad.polygon[0].y - pos.y);
        for (var i = 1; i < pad.polygon.length; i++) {
          const point = pad.polygon[i];          
          pad.path2d.lineTo(point.x - pos.x, point.y - pos.y);
        }
        pad.path2d.closePath();              
      }
    }
  }
  return pad.path2d;
}

function drawPad(ctx, pad, color, outline) {
  ctx.save();
  ctx.translate(...pad.pos);
  if(pad.shape !== 'polygon') {
    ctx.rotate(deg2rad(pad.angle));  
  } else {
    ctx.rotate(deg2rad(0));  
  }
  
  if (pad.offset) {
    ctx.translate(...pad.offset);
  }
  ctx.fillStyle = color;
  ctx.strokeStyle = color;
  var path = getCachedPadPath(pad);
  if (outline) {
    ctx.stroke(path);
  } else {
    ctx.fill(path);
  }
  ctx.restore();
}

function drawPadHole(ctx, pad, padHoleColor) {
  if (pad.type != "th") {
    return
  };

  ctx.save();
  ctx.translate(pad.holeCenterPoint.x, pad.holeCenterPoint.y);    
  ctx.rotate(deg2rad(pad.angle));
  ctx.fillStyle = padHoleColor;
  if (pad.drillshape == "oblong") {
    ctx.fill(getOblongPath(pad.drillsize));
  } else {
    ctx.fill(getCirclePath(pad.drillsize[0] / 2));
  }
  ctx.restore();
}

function drawFootprint(ctx, layer, scalefactor, footprint, padColor, padHoleColor, outlineColor, highlight, outline) {
  if (highlight) {
    // draw bounding box
    if (footprint.layer == layer) {
      ctx.save();
      ctx.globalAlpha = 0.2;
      ctx.translate(...footprint.bbox.pos);
      ctx.rotate(deg2rad(-footprint.bbox.angle));
      ctx.translate(...footprint.bbox.relpos);
      ctx.fillStyle = padColor;
      ctx.fillRect(0, 0, ...footprint.bbox.size);
      ctx.globalAlpha = 1;
      ctx.strokeStyle = padColor;
      ctx.strokeRect(0, 0, ...footprint.bbox.size);
      ctx.restore();
    }
  }
  // draw drawings
  for (var drawing of footprint.drawings) {
    if (drawing.layer == layer) {
      drawDrawing(ctx, scalefactor, drawing.drawing, padColor);
    }
  }
  // draw pads
  if (settings.renderPads) {
    for (var pad of footprint.pads) {
      if (pad.layers.includes(layer)) {
        drawPad(ctx, pad, padColor, outline);
        if (pad.pin1 && settings.highlightpin1) {
          drawPad(ctx, pad, outlineColor, true);
        }
      }
    }
    for (var pad of footprint.pads) {
      drawPadHole(ctx, pad, padHoleColor);
    }
  }
}

function drawCrosshair(canvas, x, y, scalefactor, color) {
  if(!settings.showCrosshair) {
    return;
  }

  var ctx = canvas.getContext("2d");

  HTMLFormControlsCollection.log
  
  ctx.save();
  ctx.globalAlpha = 0.75;
  ctx.lineWidth = 2 / scalefactor;
  ctx.strokeStyle = color;

  // TODO: Should calculate a proper bbox for the view port.
  const dummyOffset = 4000;

  // horz line
  ctx.beginPath();
  ctx.moveTo(x - dummyOffset, y);
  ctx.lineTo(x + dummyOffset, y);
  ctx.stroke();

  // vert line
  ctx.beginPath();
  ctx.moveTo(x, y - dummyOffset);
  ctx.lineTo(x, y + dummyOffset);
  ctx.stroke();

  ctx.globalAlpha = 1;
  ctx.restore();
}

function drawEdgeCuts(canvas, scalefactor) {
  var ctx = canvas.getContext("2d");
  var edgecolor = getComputedStyle(topmostdiv).getPropertyValue('--pcb-edge-color');
  for (var edge of pcbdata.edges) {
    drawedge(ctx, scalefactor, edge, edgecolor);
  }
}

function drawOrphanPads(canvas, layer, scalefactor, highlight, highlightedPads) {
  if (!settings.renderPads) {
    return;
  }

  highlightedPads = highlightedPads || [];

  var ctx = canvas.getContext("2d");
  ctx.save();
  ctx.lineWidth = 3 / scalefactor;
  var style = getComputedStyle(topmostdiv);
  var padColor = style.getPropertyValue('--pad-color');
  var padHoleColor = style.getPropertyValue('--pad-hole-color');  
  if (highlight) {
    padColor = style.getPropertyValue('--pad-color-highlight');
    outlineColor = style.getPropertyValue('--pin1-outline-color-highlight');
  }

  var i = 0;
  for (var pad of pcbdata.pads) {
    if (pad.layers.includes(layer)) {
      var outline = settings.renderDnpOutline;
      if (!highlight || highlightedPads.includes(i)) {
        drawPad(ctx, pad, padColor, outline); 
      }     
    }

    i++;
  }
  
  for (var pad of pcbdata.pads) {
    drawPadHole(ctx, pad, padHoleColor);
  }  

  ctx.restore();
}

function drawFootprints(canvas, layer, scalefactor, highlight) {
  var ctx = canvas.getContext("2d");
  ctx.save();
  ctx.lineWidth = 3 / scalefactor;
  var style = getComputedStyle(topmostdiv);
  var padColor = style.getPropertyValue('--pad-color');
  var padHoleColor = style.getPropertyValue('--pad-hole-color');
  var outlineColor = style.getPropertyValue('--pin1-outline-color');
  if (highlight) {
    padColor = style.getPropertyValue('--pad-color-highlight');
    outlineColor = style.getPropertyValue('--pin1-outline-color-highlight');
  }
  for (var i = 0; i < pcbdata.footprints.length; i++) {
    var mod = pcbdata.footprints[i];
    var outline = settings.renderDnpOutline && pcbdata.bom.skipped.includes(i);
    if (!highlight || highlightedFootprints.includes(i)) {
      drawFootprint(ctx, layer, scalefactor, mod, padColor, padHoleColor, outlineColor, highlight, outline);
    }
  }
  ctx.restore();
}

function drawBgLayer(layername, canvas, layer, scalefactor, edgeColor, polygonColor, textColor) {
  var ctx = canvas.getContext("2d");
  for (var d of pcbdata.drawings[layername][layer]) {
    if (["segment", "arc", "circle", "curve", "rect","polyline"].includes(d.type)) {
      drawedge(ctx, scalefactor, d, edgeColor);
    } else if (d.type == "polygon") {
      drawPolygonShape(ctx, d, polygonColor);
    } else if (d.type == "text") {
      drawText(ctx, d, polygonColor);
    }
  }
}

function drawTracks(canvas, layer, color, highlight) {
  ctx = canvas.getContext("2d");
  ctx.strokeStyle = color;
  ctx.lineCap = "round";
  for(var track of pcbdata.tracks[layer]) {
    if (highlight && highlightedNet != track.net) continue;
    ctx.lineWidth = track.width;

    if(track.type === 'polyline') {
      drawPolylineShape(ctx,track,color);
    } else if(track.type === 'polygon') {
      drawPolygonShape(ctx, track, color);
    } else if(track.type === 'text') {
      drawText(ctx, track, color);
    } else {
      if ('radius' in track) {
        ctx.beginPath();
        ctx.arc(
            ...track.center,
            track.radius,
            deg2rad(track.startangle),
            deg2rad(track.endangle));
        ctx.stroke();
      } else if('start' in track && 'end' in track) {
        ctx.beginPath();
        ctx.moveTo(...track.start);
        ctx.lineTo(...track.end);
        ctx.stroke();
      }
      
    }

  }
}

function drawZones(canvas, layer, color, highlight) {
  ctx = canvas.getContext("2d");
  ctx.save();
  ctx.strokeStyle = color;
  ctx.fillStyle = color;
  ctx.lineJoin = "round";
  for(var zone of pcbdata.zones[layer]) {
    if (!zone.path2d) {
      zone.path2d = getPolygonsPath(zone);
    }
    if (highlight && highlightedNet != zone.net) continue;
    ctx.fill(zone.path2d);
    if (zone.width > 0) {
      ctx.lineWidth = zone.width;
      ctx.stroke(zone.path2d);
    }
  }
  ctx.restore();
}

function clearCanvas(canvas, color = null) {
  var ctx = canvas.getContext("2d");
  ctx.save();
  ctx.setTransform(1, 0, 0, 1, 0, 0);
  if (color) {
    ctx.fillStyle = color;
    ctx.fillRect(0, 0, canvas.width, canvas.height);
  } else {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
  }
  ctx.restore();
}

function drawNets(canvas, layer, highlight) {
  var style = getComputedStyle(topmostdiv);
  if (settings.renderTracks) {
    var trackColor = style.getPropertyValue(highlight ? '--track-color-highlight' : '--track-color');
    drawTracks(canvas, layer, trackColor, highlight);
  }
  if (settings.renderZones) {
    var zoneColor = style.getPropertyValue(highlight ? '--zone-color-highlight' : '--zone-color');
    drawZones(canvas, layer, zoneColor, highlight);
  }
  if (highlight && settings.renderPads) {
    var padColor = style.getPropertyValue('--pad-color-highlight');
    var padHoleColor = style.getPropertyValue('--pad-hole-color');
    var ctx = canvas.getContext("2d");
    for (var footprint of pcbdata.footprints) {
      // draw pads
      var padDrawn = false;
      for (var pad of footprint.pads) {
        if (highlightedNet != pad.net) continue;
        if (pad.layers.includes(layer)) {
          drawPad(ctx, pad, padColor, false);
          padDrawn = true;
        }
      }
      if (padDrawn) {
        // redraw all pad holes because some pads may overlap
        for (var pad of footprint.pads) {
          drawPadHole(ctx, pad, padHoleColor);
        }
      }
    }
  }
}

function drawHighlightsOnLayer(canvasdict, clear = true) {
  if (clear) {
    clearCanvas(canvasdict.highlight);
  }
  
  if (highlightedFootprints.length > 0) {
    drawFootprints(canvasdict.highlight, canvasdict.layer,
      canvasdict.transform.s * canvasdict.transform.zoom, true);
  }

  // Draw crosshairs
  if (highlightedFootprints.length > 0) {
    for(var i = 0; i < pcbdata.footprints.length; i++) {
      const footprint = pcbdata.footprints[i];
      if(highlightedFootprints.includes(i) && footprint.layer === canvasdict.layer) {
        drawCrosshair(canvasdict.highlight, footprint.center[0], footprint.center[1], canvasdict.transform.s * canvasdict.transform.zoom, 'red');
      }
    }
  }

  if (highlightedNet !== null) {
    var highlightedPads = [];
    for(var i = 0; i < pcbdata.pads.length; i++) {
      var pad = pcbdata.pads[i];
      if(pad.net === highlightedNet) {
        highlightedPads.push(i);
      }
    }
    
    drawOrphanPads(canvasdict.highlight, canvasdict.layer,
      canvasdict.transform.s * canvasdict.transform.zoom, true, highlightedPads);

    drawNets(canvasdict.highlight, canvasdict.layer, true);
  }
}

function drawHighlights() {
  drawHighlightsOnLayer(allcanvas.front);
  drawHighlightsOnLayer(allcanvas.back);
}

function drawBackground(canvasdict, clear = true) {
  if (clear) {
    clearCanvas(canvasdict.bg);
    clearCanvas(canvasdict.fab);
    clearCanvas(canvasdict.silk);
  }

  drawNets(canvasdict.bg, canvasdict.layer, false);

  drawOrphanPads(canvasdict.bg, canvasdict.layer, canvasdict.transform.s * canvasdict.transform.zoom, false);

  drawFootprints(canvasdict.bg, canvasdict.layer, canvasdict.transform.s * canvasdict.transform.zoom, false);

  drawEdgeCuts(canvasdict.bg, canvasdict.transform.s);

  var style = getComputedStyle(topmostdiv);
  var edgeColor = style.getPropertyValue('--silkscreen-edge-color');
  var polygonColor = style.getPropertyValue('--silkscreen-polygon-color');
  var textColor = style.getPropertyValue('--silkscreen-text-color');
  if (settings.renderSilkscreen) {
    drawBgLayer(
      "silkscreen", canvasdict.silk, canvasdict.layer,
      canvasdict.transform.s * canvasdict.transform.zoom,
      edgeColor, polygonColor, textColor);
  }
  edgeColor = style.getPropertyValue('--fabrication-edge-color');
  polygonColor = style.getPropertyValue('--fabrication-polygon-color');
  textColor = style.getPropertyValue('--fabrication-text-color');
  if (settings.renderFabrication) {
    drawBgLayer(
      "fabrication", canvasdict.fab, canvasdict.layer,
      canvasdict.transform.s * canvasdict.transform.zoom,
      edgeColor, polygonColor, textColor);
  }
}

function prepareCanvas(canvas, flip, transform) {
  var ctx = canvas.getContext("2d");
  ctx.setTransform(1, 0, 0, 1, 0, 0);
  var fontsize = 1.55;
  ctx.scale(transform.zoom, transform.zoom);
  ctx.translate(transform.panx, transform.pany);
  if (flip) {
    ctx.scale(-1, 1);
  }
  ctx.translate(transform.x, transform.y);
  ctx.rotate(deg2rad(settings.boardRotation));
  ctx.scale(transform.s, transform.s);
}

function prepareLayer(canvasdict) {
  var flip = (canvasdict.layer == "B");
  for (var c of ["bg", "fab", "silk", "highlight"]) {
    prepareCanvas(canvasdict[c], flip, canvasdict.transform);
  }
}

function rotateVector(v, angle) {
  angle = deg2rad(angle);
  return [
    v[0] * Math.cos(angle) - v[1] * Math.sin(angle),
    v[0] * Math.sin(angle) + v[1] * Math.cos(angle)
  ];
}

function applyRotation(bbox) {
  var corners = [
    [bbox.minx, bbox.miny],
    [bbox.minx, bbox.maxy],
    [bbox.maxx, bbox.miny],
    [bbox.maxx, bbox.maxy],
  ];
  corners = corners.map((v) => rotateVector(v, settings.boardRotation));
  return {
    minx: corners.reduce((a, v) => Math.min(a, v[0]), Infinity),
    miny: corners.reduce((a, v) => Math.min(a, v[1]), Infinity),
    maxx: corners.reduce((a, v) => Math.max(a, v[0]), -Infinity),
    maxy: corners.reduce((a, v) => Math.max(a, v[1]), -Infinity),
  }
}

function recalcLayerScale(layerdict, width, height) {
  var bbox = applyRotation(pcbdata.edges_bbox);
  var scalefactor = 0.98 * Math.min(
    width / (bbox.maxx - bbox.minx),
    height / (bbox.maxy - bbox.miny)
  );
  if (scalefactor < 0.1) {
    scalefactor = 1;
  }
  layerdict.transform.s = scalefactor;
  var flip = (layerdict.layer == "B");
  if (flip) {
    layerdict.transform.x = -((bbox.maxx + bbox.minx) * scalefactor + width) * 0.5;
  } else {
    layerdict.transform.x = -((bbox.maxx + bbox.minx) * scalefactor - width) * 0.5;
  }
  layerdict.transform.y = -((bbox.maxy + bbox.miny) * scalefactor - height) * 0.5;
  for (var c of ["bg", "fab", "silk", "highlight"]) {
    canvas = layerdict[c];
    canvas.width = width;
    canvas.height = height;
    canvas.style.width = (width / devicePixelRatio) + "px";
    canvas.style.height = (height / devicePixelRatio) + "px";
  }
}

function redrawCanvas(layerdict) {
  prepareLayer(layerdict);
  drawBackground(layerdict);
  drawHighlightsOnLayer(layerdict);
}

function resizeCanvas(layerdict) {
  var canvasdivid = {
    "F": "frontcanvas",
    "B": "backcanvas"
  } [layerdict.layer];
  var width = document.getElementById(canvasdivid).clientWidth * devicePixelRatio;
  var height = document.getElementById(canvasdivid).clientHeight * devicePixelRatio;
  recalcLayerScale(layerdict, width, height);
  redrawCanvas(layerdict);
}

function resizeAll() {
  resizeCanvas(allcanvas.front);
  resizeCanvas(allcanvas.back);
}

function pointWithinDistanceToSegment(x, y, x1, y1, x2, y2, d) {
  var A = x - x1;
  var B = y - y1;
  var C = x2 - x1;
  var D = y2 - y1;

  var dot = A * C + B * D;
  var len_sq = C * C + D * D;
  var dx, dy;
  if (len_sq == 0) {
    // start and end of the segment coincide
    dx = x - x1;
    dy = y - y1;
  } else {
    var param = dot / len_sq;
    var xx, yy;
    if (param < 0) {
      xx = x1;
      yy = y1;
    } else if (param > 1) {
      xx = x2;
      yy = y2;
    } else {
      xx = x1 + param * C;
      yy = y1 + param * D;
    }
    dx = x - xx;
    dy = y - yy;
  }
  return dx * dx + dy * dy <= d * d;
}

function modulo(n, mod) {
  return ((n % mod) + mod ) % mod;
}

function pointWithinDistanceToArc(x, y, xc, yc, radius, startangle, endangle, d) {
  var dx = x - xc;
  var dy = y - yc;
  var r_sq = dx * dx + dy * dy;
  var rmin = Math.max(0, radius-d);
  var rmax = radius + d;

  if (r_sq < rmin * rmin || r_sq > rmax * rmax)
    return false;

  var angle1 = modulo(deg2rad(startangle), 2 * Math.PI);
  var dx1 = xc + radius * Math.cos(angle1) - x;
  var dy1 = yc + radius * Math.sin(angle1) - y;
  if (dx1 * dx1 + dy1 * dy1 <= d * d)
    return true;

  var angle2 = modulo(deg2rad(endangle), 2 * Math.PI);
  var dx2 = xc + radius * Math.cos(angle2) - x;
  var dy2 = yc + radius * Math.sin(angle2) - y;
  if (dx2 * dx2 + dy2 * dy2 <= d * d)
    return true;

  var angle = modulo(Math.atan2(dy, dx), 2 * Math.PI);
  if (angle1 > angle2)
    return (angle >= angle2 || angle <= angle1);
  else
    return (angle >= angle1 && angle <= angle2);
}

function pointWithinPad(x, y, pad) {
  var v = [x - pad.pos[0], y - pad.pos[1]];
  v = rotateVector(v, -pad.angle);
  if (pad.offset) {
    v[0] -= pad.offset[0];
    v[1] -= pad.offset[1];
  }
  return emptyContext2d.isPointInPath(getCachedPadPath(pad), ...v);
}

function netHitScan(layer, x, y) {
  // TODO: Should be refactored

  // Check track segments
  if (settings.renderTracks && pcbdata.tracks) {
    for(var track of pcbdata.tracks[layer]) {
      if(track.type === 'polyline') {
        const path = getPolygonsPath(track);
        if(path) {
          hitTestContext2d.save();
          hitTestContext2d.lineWidth = track.width;
          
          if(hitTestContext2d.isPointInStroke(path,x,y)) {
            hitTestContext2d.restore();
            return track.net;            
          }
          
          hitTestContext2d.restore();          
        }        

      } else if(track.type === 'polygon') {
        const path = getPolygonsPath(track);
        if(path && hitTestContext2d.isPointInPath(path,x,y)) {          
          return track.net;                      
        }                     
      } else if(track.type === 'text') {
        // TODO: To implement taking in account TrueType fonts.
      } else if ('radius' in track) {
        if (pointWithinDistanceToArc(x, y, ...track.center, track.radius, track.startangle, track.endangle, track.width / 2)) {
          return track.net;
        }
      } else if('start' in track && 'end' in track) {
        if (pointWithinDistanceToSegment(x, y, ...track.start, ...track.end, track.width / 2)) {
          return track.net;
        }
      }
    }
  }
  // Check pads
  if (settings.renderPads) {

    // Footprints containing pads
    for (var footprint of pcbdata.footprints) {
      for(var pad of footprint.pads) {
        if (pad.layers.includes(layer) && pointWithinPad(x, y, pad)) {
          return pad.net;
        }
      }
    }

    // Orphan pads
    for(var pad of pcbdata.pads) {
      if (pad.layers.includes(layer) && pointWithinPad(x, y, pad)) {
        return pad.net;
      }
    }
  }
  return null;
}

function pointWithinFootprintBbox(x, y, bbox) {
  var v = [x - bbox.pos[0], y - bbox.pos[1]];
  v = rotateVector(v, bbox.angle);
  return bbox.relpos[0] <= v[0] && v[0] <= bbox.relpos[0] + bbox.size[0] &&
         bbox.relpos[1] <= v[1] && v[1] <= bbox.relpos[1] + bbox.size[1];
}

function bboxHitScan(layer, x, y) {
  var result = [];
  for (var i = 0; i < pcbdata.footprints.length; i++) {
    var footprint = pcbdata.footprints[i];
    if (footprint.layer == layer) {
      if (pointWithinFootprintBbox(x, y, footprint.bbox)) {
        result.push(i);
      }
    }
  }
  return result;
}

function handlePointerDown(e, layerdict) {
  if (e.button != 0 && e.button != 1) {
    return;
  }
  e.preventDefault();
  e.stopPropagation();

  if (!e.hasOwnProperty("offsetX")) {
    // The polyfill doesn't set this properly
    e.offsetX = e.pageX - e.currentTarget.offsetLeft;
    e.offsetY = e.pageY - e.currentTarget.offsetTop;
  }

  layerdict.pointerStates[e.pointerId] = {
    distanceTravelled: 0,
    lastX: e.offsetX,
    lastY: e.offsetY,
    downTime: Date.now(),
  };
}

function handleMouseClick(e, layerdict) {
  if (!e.hasOwnProperty("offsetX")) {
    // The polyfill doesn't set this properly
    e.offsetX = e.pageX - e.currentTarget.offsetLeft;
    e.offsetY = e.pageY - e.currentTarget.offsetTop;
  }

  var x = e.offsetX;
  var y = e.offsetY;
  var t = layerdict.transform;
  if (layerdict.layer == "B") {
    x = (devicePixelRatio * x / t.zoom - t.panx + t.x) / -t.s;
  } else {
    x = (devicePixelRatio * x / t.zoom - t.panx - t.x) / t.s;
  }
  y = (devicePixelRatio * y / t.zoom - t.y - t.pany) / t.s;
  var v = rotateVector([x, y], -settings.boardRotation);
  if ("nets" in pcbdata) {
    var net = netHitScan(layerdict.layer, ...v);
    if (net !== highlightedNet) {
      netClicked(net);
    }
  }
  if (highlightedNet === null) {
    var footprints = bboxHitScan(layerdict.layer, ...v);
    if (footprints.length > 0) {
      footprintsClicked(footprints);
    }
  }
}

function handlePointerLeave(e, layerdict) {
  e.preventDefault();
  e.stopPropagation();

  if (!settings.redrawOnDrag) {
    redrawCanvas(layerdict);
  }

  delete layerdict.pointerStates[e.pointerId];
}

function resetTransform(layerdict) {
  layerdict.transform.panx = 0;
  layerdict.transform.pany = 0;
  layerdict.transform.zoom = 1;
  redrawCanvas(layerdict);
}

function handlePointerUp(e, layerdict) {
  if (!e.hasOwnProperty("offsetX")) {
    // The polyfill doesn't set this properly
    e.offsetX = e.pageX - e.currentTarget.offsetLeft;
    e.offsetY = e.pageY - e.currentTarget.offsetTop;
  }

  e.preventDefault();
  e.stopPropagation();

  if (e.button == 2) {
    // Reset pan and zoom on right click.
    resetTransform(layerdict);
    layerdict.anotherPointerTapped = false;
    return;
  }

  // We haven't necessarily had a pointermove event since the interaction started, so make sure we update this now
  var ptr = layerdict.pointerStates[e.pointerId];
  ptr.distanceTravelled += Math.abs(e.offsetX - ptr.lastX) + Math.abs(e.offsetY - ptr.lastY);

  if (e.button == 0 && ptr.distanceTravelled < 10 && Date.now() - ptr.downTime <= 500) {
    if (Object.keys(layerdict.pointerStates).length == 1) {
      if (layerdict.anotherPointerTapped) {
        // This is the second pointer coming off of a two-finger tap
        resetTransform(layerdict);
      } else {
        // This is just a regular tap
        handleMouseClick(e, layerdict);
      }
      layerdict.anotherPointerTapped = false;
    } else {
      // This is the first finger coming off of what could become a two-finger tap
      layerdict.anotherPointerTapped = true;
    }
  } else {
    if (!settings.redrawOnDrag) {
      redrawCanvas(layerdict);
    }
    layerdict.anotherPointerTapped = false;
  }

  delete layerdict.pointerStates[e.pointerId];
}

function handlePointerMove(e, layerdict) {
  if (!layerdict.pointerStates.hasOwnProperty(e.pointerId)) {
    return;
  }
  e.preventDefault();
  e.stopPropagation();

  if (!e.hasOwnProperty("offsetX")) {
    // The polyfill doesn't set this properly
    e.offsetX = e.pageX - e.currentTarget.offsetLeft;
    e.offsetY = e.pageY - e.currentTarget.offsetTop;
  }

  var thisPtr = layerdict.pointerStates[e.pointerId];

  var dx = e.offsetX - thisPtr.lastX;
  var dy = e.offsetY - thisPtr.lastY;

  // If this number is low on pointer up, we count the action as a click
  thisPtr.distanceTravelled += Math.abs(dx) + Math.abs(dy);

  if (Object.keys(layerdict.pointerStates).length == 1) {
    // This is a simple drag
    layerdict.transform.panx += devicePixelRatio * dx / layerdict.transform.zoom;
    layerdict.transform.pany += devicePixelRatio * dy / layerdict.transform.zoom;
  } else if (Object.keys(layerdict.pointerStates).length == 2) {
    var otherPtr = Object.values(layerdict.pointerStates).filter((ptr) => ptr != thisPtr)[0];

    var oldDist = Math.sqrt(Math.pow(thisPtr.lastX - otherPtr.lastX, 2) + Math.pow(thisPtr.lastY - otherPtr.lastY, 2));
    var newDist = Math.sqrt(Math.pow(e.offsetX - otherPtr.lastX, 2)     + Math.pow(e.offsetY - otherPtr.lastY, 2));

    var scaleFactor = newDist/oldDist;

    if (scaleFactor != NaN) {
      layerdict.transform.zoom *= scaleFactor;

      var zoomd = (1 - scaleFactor) / layerdict.transform.zoom;
      layerdict.transform.panx += devicePixelRatio * otherPtr.lastX * zoomd;
      layerdict.transform.pany += devicePixelRatio * otherPtr.lastY * zoomd;
    }
  }

  thisPtr.lastX = e.offsetX;
  thisPtr.lastY = e.offsetY;

  if (settings.redrawOnDrag) {
    redrawCanvas(layerdict);
  }
}

function handleMouseWheel(e, layerdict) {
  e.preventDefault();
  e.stopPropagation();
  var t = layerdict.transform;
  var wheeldelta = e.deltaY;
  if (e.deltaMode == 1) {
    // FF only, scroll by lines
    wheeldelta *= 30;
  } else if (e.deltaMode == 2) {
    wheeldelta *= 300;
  }
  var m = Math.pow(1.1, -wheeldelta / 40);
  // Limit amount of zoom per tick.
  if (m > 2) {
    m = 2;
  } else if (m < 0.5) {
    m = 0.5;
  }
  t.zoom *= m;
  var zoomd = (1 - m) / t.zoom;
  t.panx += devicePixelRatio * e.offsetX * zoomd;
  t.pany += devicePixelRatio * e.offsetY * zoomd;
  redrawCanvas(layerdict);
}

function addMouseHandlers(div, layerdict) {
  div.addEventListener("pointerdown", function(e) {
    handlePointerDown(e, layerdict);
  });
  div.addEventListener("pointermove", function(e) {
    handlePointerMove(e, layerdict);
  });
  div.addEventListener("pointerup", function(e) {
    handlePointerUp(e, layerdict);
  });
  var pointerleave = function(e) {
    handlePointerLeave(e, layerdict);
  }
  div.addEventListener("pointercancel", pointerleave);
  div.addEventListener("pointerleave", pointerleave);
  div.addEventListener("pointerout", pointerleave);

  div.onwheel = function(e) {
    handleMouseWheel(e, layerdict);
  }
  for (var element of [div, layerdict.bg, layerdict.fab, layerdict.silk, layerdict.highlight]) {
    element.addEventListener("contextmenu", function(e) {
      e.preventDefault();
    }, false);
  }
}

function setRedrawOnDrag(value) {
  settings.redrawOnDrag = value;
  writeStorage("redrawOnDrag", value);
}

function setShowCrosshair(value) {
  settings.showCrosshair = value;
  writeStorage("showCrosshair", value);
}

function setBoardRotation(value) {
  settings.boardRotation = value * 5;
  writeStorage("boardRotation", settings.boardRotation);
  document.getElementById("rotationDegree").textContent = settings.boardRotation;
  resizeAll();
}

function initRender() {
  allcanvas = {
    front: {
      transform: {
        x: 0,
        y: 0,
        s: 1,
        panx: 0,
        pany: 0,
        zoom: 1,
      },
      pointerStates: {},
      anotherPointerTapped: false,
      bg: document.getElementById("F_bg"),
      fab: document.getElementById("F_fab"),
      silk: document.getElementById("F_slk"),
      highlight: document.getElementById("F_hl"),
      layer: "F",
    },
    back: {
      transform: {
        x: 0,
        y: 0,
        s: 1,
        panx: 0,
        pany: 0,
        zoom: 1,
      },
      pointerStates: {},
      anotherPointerTapped: false,
      bg: document.getElementById("B_bg"),
      fab: document.getElementById("B_fab"),
      silk: document.getElementById("B_slk"),
      highlight: document.getElementById("B_hl"),
      layer: "B",
    }
  };
  addMouseHandlers(document.getElementById("frontcanvas"), allcanvas.front);
  addMouseHandlers(document.getElementById("backcanvas"), allcanvas.back);
}

///////////////////////////////////////////////

///////////////////////////////////////////////
/* DOM manipulation and misc code */

var bomsplit;
var canvassplit;
var initDone = false;
var bomSortFunction = null;
var currentSortColumn = null;
var currentSortOrder = null;
var currentHighlightedRowId;
var highlightHandlers = [];
var footprintIndexToHandler = {};
var netsToHandler = {};
var highlightedFootprints = [];
var highlightedOrphanPads = [];
var highlightedNet = null;
var lastClicked;

function dbg(html) {
  dbgdiv.innerHTML = html;
}

function redrawIfInitDone() {
  if (initDone) {
    redrawCanvas(allcanvas.front);
    redrawCanvas(allcanvas.back);
  }
}

function padsVisible(value) {
  writeStorage("padsVisible", value);
  settings.renderPads = value;
  redrawIfInitDone();
}

function referencesVisible(value) {
  writeStorage("referencesVisible", value);
  settings.renderReferences = value;
  redrawIfInitDone();
}

function valuesVisible(value) {
  writeStorage("valuesVisible", value);
  settings.renderValues = value;
  redrawIfInitDone();
}

function tracksVisible(value) {
  writeStorage("tracksVisible", value);
  settings.renderTracks = value;
  redrawIfInitDone();
}

function zonesVisible(value) {
  writeStorage("zonesVisible", value);
  settings.renderZones = value;
  redrawIfInitDone();
}

function dnpOutline(value) {
  writeStorage("dnpOutline", value);
  settings.renderDnpOutline = value;
  redrawIfInitDone();
}

function setDarkMode(value) {
  if (value) {
    topmostdiv.classList.add("dark");
  } else {
    topmostdiv.classList.remove("dark");
  }
  writeStorage("darkmode", value);
  settings.darkMode = value;
  redrawIfInitDone();
}

function setFullscreen(value) {
  if (value) {
    document.documentElement.requestFullscreen();
  } else {
    document.exitFullscreen();
  }
}

function fabricationVisible(value) {
  writeStorage("fabricationVisible", value);
  settings.renderFabrication = value;
  redrawIfInitDone();
}

function silkscreenVisible(value) {
  writeStorage("silkscreenVisible", value);
  settings.renderSilkscreen = value;
  redrawIfInitDone();
}

function setHighlightPin1(value) {
  writeStorage("highlightpin1", value);
  settings.highlightpin1 = value;
  redrawIfInitDone();
}

function getStoredCheckboxRefs(checkbox) {
  function convert(ref) {
    var intref = parseInt(ref);
    if (isNaN(intref)) {
      for (var i = 0; i < pcbdata.footprints.length; i++) {
        if (pcbdata.footprints[i].ref == ref) {
          return i;
        }
      }
      return -1;
    } else {
      return intref;
    }
  }
  if (!(checkbox in settings.checkboxStoredRefs)) {
    var val = readStorage("checkbox_" + checkbox);
    settings.checkboxStoredRefs[checkbox] = val ? val : "";
  }
  if (!settings.checkboxStoredRefs[checkbox]) {
    return new Set();
  } else {
    return new Set(settings.checkboxStoredRefs[checkbox].split(",").map(r => convert(r)).filter(a => a >= 0));
  }
}

function getCheckboxState(checkbox, references) {
  var storedRefsSet = getStoredCheckboxRefs(checkbox);
  var currentRefsSet = new Set(references.map(r => r[1]));
  // Get difference of current - stored
  var difference = new Set(currentRefsSet);
  for (ref of storedRefsSet) {
    difference.delete(ref);
  }
  if (difference.size == 0) {
    // All the current refs are stored
    return "checked";
  } else if (difference.size == currentRefsSet.size) {
    // None of the current refs are stored
    return "unchecked";
  } else {
    // Some of the refs are stored
    return "indeterminate";
  }
}

function setBomCheckboxState(checkbox, element, references) {
  var state = getCheckboxState(checkbox, references);
  element.checked = (state == "checked");
  element.indeterminate = (state == "indeterminate");
}

function createCheckboxChangeHandler(checkbox, references, row) {
  return function() {
    refsSet = getStoredCheckboxRefs(checkbox);
    var darkenWhenChecked = settings.darkenWhenChecked == checkbox;
    eventArgs = {
      checkbox: checkbox,
      refs: references,
    }
    if (this.checked) {
      // checkbox ticked
      for (var ref of references) {
        refsSet.add(ref[1]);
      }
      if (darkenWhenChecked) {
        row.classList.add("checked");
      }
      eventArgs.state = 'checked';
    } else {
      // checkbox unticked
      for (var ref of references) {
        refsSet.delete(ref[1]);
      }
      if (darkenWhenChecked) {
        row.classList.remove("checked");
      }
      eventArgs.state = 'unchecked';
    }
    settings.checkboxStoredRefs[checkbox] = [...refsSet].join(",");
    writeStorage("checkbox_" + checkbox, settings.checkboxStoredRefs[checkbox]);
    updateCheckboxStats(checkbox);
    EventHandler.emitEvent(IBOM_EVENT_TYPES.CHECKBOX_CHANGE_EVENT, eventArgs);
  }
}

function clearHighlightedFootprints() {
  if (currentHighlightedRowId) {
    document.getElementById(currentHighlightedRowId).classList.remove("highlighted");
    currentHighlightedRowId = null;
    highlightedFootprints = [];  
    highlightedOrphanPads = [];  
    highlightedNet = null;
  }
}

function createRowHighlightHandler(rowid, refs, net) {
  return function() {
    if (currentHighlightedRowId) {
      if (currentHighlightedRowId == rowid) {
        return;
      }
      document.getElementById(currentHighlightedRowId).classList.remove("highlighted");
    }
    document.getElementById(rowid).classList.add("highlighted");
    currentHighlightedRowId = rowid;
    highlightedFootprints = refs ? refs.map(r => r[1]) : [];
    highlightedNet = net;
    drawHighlights();
    EventHandler.emitEvent(
      IBOM_EVENT_TYPES.HIGHLIGHT_EVENT,
      {
        rowid: rowid,
        refs: refs,
        net: net
      });
  }
}

function entryMatches(entry) {
  if (settings.bommode == "netlist") {
    // entry is just a net name
    return entry.toLowerCase().indexOf(filter) >= 0;
  }
  // check refs
  for (var ref of entry[3]) {
    if (ref[0].toLowerCase().indexOf(filter) >= 0) {
      return true;
    }
  }
  // check extra fields
  for (var i in config.extra_fields) {
    if (entry[4][i].toLowerCase().indexOf(filter) >= 0) {
      return true;
    }
  }
  // check value
  if (entry[1].toLowerCase().indexOf(filter) >= 0) {
    return true;
  }
  // check footprint
  if (entry[2].toLowerCase().indexOf(filter) >= 0) {
    return true;
  }
  return false;
}

function findRefInEntry(entry) {
  return entry[3].filter(r => r[0].toLowerCase() == reflookup);
}

function highlightFilter(s) {
  if (!filter) {
    return s;
  }
  var parts = s.toLowerCase().split(filter);
  if (parts.length == 1) {
    return s;
  }
  var r = "";
  var pos = 0;
  for (var i in parts) {
    if (i > 0) {
      r += '<mark class="highlight">' +
        s.substring(pos, pos + filter.length) +
        '</mark>';
      pos += filter.length;
    }
    r += s.substring(pos, pos + parts[i].length);
    pos += parts[i].length;
  }
  return r;
}

function checkboxSetUnsetAllHandler(checkboxname) {
  return function() {
    var checkboxnum = 0;
    while (checkboxnum < settings.checkboxes.length &&
      settings.checkboxes[checkboxnum].toLowerCase() != checkboxname.toLowerCase()) {
      checkboxnum++;
    }
    if (checkboxnum >= settings.checkboxes.length) {
      return;
    }
    var allset = true;
    var checkbox;
    var row;
    for (row of bombody.childNodes) {
      checkbox = row.childNodes[checkboxnum + 1].childNodes[0];
      if (!checkbox.checked || checkbox.indeterminate) {
        allset = false;
        break;
      }
    }
    for (row of bombody.childNodes) {
      checkbox = row.childNodes[checkboxnum + 1].childNodes[0];
      checkbox.checked = !allset;
      checkbox.indeterminate = false;
      checkbox.onchange();
    }
  }
}

function createColumnHeader(name, cls, comparator) {
  var th = document.createElement("TH");
  th.innerHTML = name;
  th.classList.add(cls);
  th.style.cursor = "pointer";
  var span = document.createElement("SPAN");
  span.classList.add("sortmark");
  span.classList.add("none");
  th.appendChild(span);
  th.onclick = function() {
    if (currentSortColumn && this !== currentSortColumn) {
      // Currently sorted by another column
      currentSortColumn.childNodes[1].classList.remove(currentSortOrder);
      currentSortColumn.childNodes[1].classList.add("none");
      currentSortColumn = null;
      currentSortOrder = null;
    }
    if (currentSortColumn && this === currentSortColumn) {
      // Already sorted by this column
      if (currentSortOrder == "asc") {
        // Sort by this column, descending order
        bomSortFunction = function(a, b) {
          return -comparator(a, b);
        }
        currentSortColumn.childNodes[1].classList.remove("asc");
        currentSortColumn.childNodes[1].classList.add("desc");
        currentSortOrder = "desc";
      } else {
        // Unsort
        bomSortFunction = null;
        currentSortColumn.childNodes[1].classList.remove("desc");
        currentSortColumn.childNodes[1].classList.add("none");
        currentSortColumn = null;
        currentSortOrder = null;
      }
    } else {
      // Sort by this column, ascending order
      bomSortFunction = comparator;
      currentSortColumn = this;
      currentSortColumn.childNodes[1].classList.remove("none");
      currentSortColumn.childNodes[1].classList.add("asc");
      currentSortOrder = "asc";
    }
    populateBomBody();
  }
  return th;
}

function populateBomHeader() {
  while (bomhead.firstChild) {
    bomhead.removeChild(bomhead.firstChild);
  }
  var tr = document.createElement("TR");
  var th = document.createElement("TH");
  th.classList.add("numCol");
  tr.appendChild(th);
  var checkboxCompareClosure = function(checkbox) {
    return (a, b) => {
      var stateA = getCheckboxState(checkbox, a[3]);
      var stateB = getCheckboxState(checkbox, b[3]);
      if (stateA > stateB) return -1;
      if (stateA < stateB) return 1;
      return 0;
    }
  }
  if (settings.bommode == "netlist") {
    th = createColumnHeader("Net name", "bom-netname", (a, b) => {
      if (a > b) return -1;
      if (a < b) return 1;
      return 0;
    });
    tr.appendChild(th);
  } else {
    for (var checkbox of settings.checkboxes) {
      th = createColumnHeader(
        checkbox, "bom-checkbox", checkboxCompareClosure(checkbox));
      th.onclick = fancyDblClickHandler(
        th, th.onclick.bind(th), checkboxSetUnsetAllHandler(checkbox));
      tr.appendChild(th);
    }
    tr.appendChild(createColumnHeader("References", "References", (a, b) => {
      var i = 0;
      while (i < a[3].length && i < b[3].length) {
        if (a[3][i] != b[3][i]) return a[3][i] > b[3][i] ? 1 : -1;
        i++;
      }
      return a[3].length - b[3].length;
    }));
    // Extra fields
    if (config.extra_fields.length > 0) {
      var extraFieldCompareClosure = function(fieldIndex) {
        return (a, b) => {
          var fa = a[4][fieldIndex];
          var fb = b[4][fieldIndex];
          if (fa != fb) return fa > fb ? 1 : -1;
          else return 0;
        }
      }
      for (var i in config.extra_fields) {
        tr.appendChild(createColumnHeader(
          config.extra_fields[i], "extra", extraFieldCompareClosure(i)));
      }
    }
    tr.appendChild(createColumnHeader("Value", "Value", (a, b) => {
      return valueCompare(a[5], b[5], a[1], b[1]);
    }));
    tr.appendChild(createColumnHeader("Footprint", "Footprint", (a, b) => {
      if (a[2] != b[2]) return a[2] > b[2] ? 1 : -1;
      else return 0;
    }));
    if (settings.bommode == "grouped") {
      tr.appendChild(createColumnHeader("Quantity", "Quantity", (a, b) => {
        return a[3].length - b[3].length;
      }));
    }
  }
  bomhead.appendChild(tr);
}

function populateBomBody() {
  while (bom.firstChild) {
    bom.removeChild(bom.firstChild);
  }
  highlightHandlers = [];
  footprintIndexToHandler = {};
  netsToHandler = {};
  currentHighlightedRowId = null;
  var first = true;
  if (settings.bommode == "netlist") {
    bomtable = pcbdata.nets.slice();
  } else {
    switch (settings.canvaslayout) {
      case 'F':
        bomtable = pcbdata.bom.F.slice();
        break;
      case 'FB':
        bomtable = pcbdata.bom.both.slice();
        break;
      case 'B':
        bomtable = pcbdata.bom.B.slice();
        break;
    }
    if (settings.bommode == "ungrouped") {
      // expand bom table
      expandedTable = []
      for (var bomentry of bomtable) {
        for (var ref of bomentry[3]) {
          expandedTable.push([1, bomentry[1], bomentry[2], [ref], bomentry[4], bomentry[5]]);
        }
      }
      bomtable = expandedTable;
    }
  }
  if (bomSortFunction) {
    bomtable = bomtable.sort(bomSortFunction);
  }
  for (var i in bomtable) {
    var bomentry = bomtable[i];
    if (filter && !entryMatches(bomentry)) {
      continue;
    }
    var references = null;
    var netname = null;
    var tr = document.createElement("TR");
    var td = document.createElement("TD");
    var rownum = +i + 1;
    tr.id = "bomrow" + rownum;
    td.textContent = rownum;
    tr.appendChild(td);
    if (settings.bommode == "netlist") {
      netname = bomentry;
      td = document.createElement("TD");
      td.innerHTML = highlightFilter(netname ? netname : "&lt;no net&gt;");
      tr.appendChild(td);
    } else {
      if (reflookup) {
        references = findRefInEntry(bomentry);
        if (references.length == 0) {
          continue;
        }
      } else {
        references = bomentry[3];
      }
      // Checkboxes
      for (var checkbox of settings.checkboxes) {
        if (checkbox) {
          td = document.createElement("TD");
          var input = document.createElement("input");
          input.type = "checkbox";
          input.onchange = createCheckboxChangeHandler(checkbox, references, tr);
          setBomCheckboxState(checkbox, input, references);
          if (input.checked && settings.darkenWhenChecked == checkbox) {
            tr.classList.add("checked");
          }
          td.appendChild(input);
          tr.appendChild(td);
        }
      }
      // References
      td = document.createElement("TD");
      td.innerHTML = highlightFilter(references.map(r => r[0]).join(", "));
      tr.appendChild(td);
      // Extra fields
      for (var i in config.extra_fields) {
        td = document.createElement("TD");
        td.innerHTML = highlightFilter(bomentry[4][i]);
        tr.appendChild(td);
      }
      // Value
      td = document.createElement("TD");
      td.innerHTML = highlightFilter(bomentry[1]);
      tr.appendChild(td);
      // Footprint
      td = document.createElement("TD");
      td.innerHTML = highlightFilter(bomentry[2]);
      tr.appendChild(td);
      if (settings.bommode == "grouped") {
        // Quantity
        td = document.createElement("TD");
        td.textContent = bomentry[3].length;
        tr.appendChild(td);
      }
    }
    bom.appendChild(tr);
    var handler = createRowHighlightHandler(tr.id, references, netname);
    tr.onmousemove = handler;
    highlightHandlers.push({
      id: tr.id,
      handler: handler,
    });
    if (references !== null) {
      for (var refIndex of references.map(r => r[1])) {
        footprintIndexToHandler[refIndex] = handler;
      }
    }
    if (netname !== null) {
      netsToHandler[netname] = handler;
    }
    if ((filter || reflookup) && first) {
      handler();
      first = false;
    }
  }
  EventHandler.emitEvent(
    IBOM_EVENT_TYPES.BOM_BODY_CHANGE_EVENT,
    {
      filter: filter,
      reflookup: reflookup,
      checkboxes: settings.checkboxes,
      bommode: settings.bommode,
    });
}

function highlightPreviousRow() {
  if (!currentHighlightedRowId) {
    highlightHandlers[highlightHandlers.length - 1].handler();
  } else {
    if (highlightHandlers.length > 1 &&
      highlightHandlers[0].id == currentHighlightedRowId) {
      highlightHandlers[highlightHandlers.length - 1].handler();
    } else {
      for (var i = 0; i < highlightHandlers.length - 1; i++) {
        if (highlightHandlers[i + 1].id == currentHighlightedRowId) {
          highlightHandlers[i].handler();
          break;
        }
      }
    }
  }
  smoothScrollToRow(currentHighlightedRowId);
}

function highlightNextRow() {
  if (!currentHighlightedRowId) {
    highlightHandlers[0].handler();
  } else {
    if (highlightHandlers.length > 1 &&
      highlightHandlers[highlightHandlers.length - 1].id == currentHighlightedRowId) {
      highlightHandlers[0].handler();
    } else {
      for (var i = 1; i < highlightHandlers.length; i++) {
        if (highlightHandlers[i - 1].id == currentHighlightedRowId) {
          highlightHandlers[i].handler();
          break;
        }
      }
    }
  }
  smoothScrollToRow(currentHighlightedRowId);
}

function populateBomTable() {
  populateBomHeader();
  populateBomBody();
}

function footprintsClicked(footprintIndexes) {
  var lastClickedIndex = footprintIndexes.indexOf(lastClicked);
  for (var i = 1; i <= footprintIndexes.length; i++) {
    var refIndex = footprintIndexes[(lastClickedIndex + i) % footprintIndexes.length];
    if (refIndex in footprintIndexToHandler) {
      lastClicked = refIndex;
      footprintIndexToHandler[refIndex]();
      smoothScrollToRow(currentHighlightedRowId);
      break;
    }
  }
}

function netClicked(net) {
  if (net in netsToHandler) {
    netsToHandler[net]();
    smoothScrollToRow(currentHighlightedRowId);
  } else {
    clearHighlightedFootprints();
    highlightedNet = net;
    drawHighlights();
  }
}

function updateFilter(input) {
  filter = input.toLowerCase();
  populateBomTable();
}

function updateRefLookup(input) {
  reflookup = input.toLowerCase();
  populateBomTable();
}

function changeCanvasLayout(layout) {
  document.getElementById("fl-btn").classList.remove("depressed");
  document.getElementById("fb-btn").classList.remove("depressed");
  document.getElementById("bl-btn").classList.remove("depressed");
  switch (layout) {
    case 'F':
      document.getElementById("fl-btn").classList.add("depressed");
      if (settings.bomlayout != "bom-only") {
        canvassplit.collapse(1);
      }
      break;
    case 'B':
      document.getElementById("bl-btn").classList.add("depressed");
      if (settings.bomlayout != "bom-only") {
        canvassplit.collapse(0);
      }
      break;
    default:
      document.getElementById("fb-btn").classList.add("depressed");
      if (settings.bomlayout != "bom-only") {
        canvassplit.setSizes([50, 50]);
      }
  }
  settings.canvaslayout = layout;
  writeStorage("canvaslayout", layout);
  resizeAll();
  changeBomMode(settings.bommode);
}

function populateMetadata() {
  document.getElementById("title").innerHTML = pcbdata.metadata.title;
  document.getElementById("revision").innerHTML = "Rev: " + pcbdata.metadata.revision;
  document.getElementById("company").innerHTML = pcbdata.metadata.company;
  document.getElementById("filedate").innerHTML = pcbdata.metadata.date;
  if (pcbdata.metadata.title != "") {
    document.title = pcbdata.metadata.title + " BOM";
  }
  // Calculate board stats
  var fp_f = 0, fp_b = 0, pads_f = 0, pads_b = 0, pads_th = 0;
  for (var i = 0; i < pcbdata.footprints.length; i++) {
    if (pcbdata.bom.skipped.includes(i)) continue;
    var mod = pcbdata.footprints[i];
    if (mod.layer == "F") {
      fp_f++;
    } else {
      fp_b++;
    }
    for (var pad of mod.pads) {
      if (pad.type == "th") {
        pads_th++;
      } else {
        if (pad.layers.includes("F")) {
          pads_f++;
        }
        if (pad.layers.includes("B")) {
          pads_b++;
        }
      }
    }
  }
  document.getElementById("stats-components-front").innerHTML = fp_f;
  document.getElementById("stats-components-back").innerHTML = fp_b;
  document.getElementById("stats-components-total").innerHTML = fp_f + fp_b;
  document.getElementById("stats-groups-front").innerHTML = pcbdata.bom.F.length;
  document.getElementById("stats-groups-back").innerHTML = pcbdata.bom.B.length;
  document.getElementById("stats-groups-total").innerHTML = pcbdata.bom.both.length;
  document.getElementById("stats-smd-pads-front").innerHTML = pads_f;
  document.getElementById("stats-smd-pads-back").innerHTML = pads_b;
  document.getElementById("stats-smd-pads-total").innerHTML = pads_f + pads_b;
  document.getElementById("stats-th-pads").innerHTML = pads_th;
  // Update version string
  document.getElementById("github-link").innerHTML = "InteractiveHtmlBom&nbsp;" +
    /^v\d+\.\d+/.exec(pcbdata.ibom_version)[0];
}

function changeBomLayout(layout) {
  document.getElementById("bom-btn").classList.remove("depressed");
  document.getElementById("lr-btn").classList.remove("depressed");
  document.getElementById("tb-btn").classList.remove("depressed");
  switch (layout) {
    case 'bom-only':
      document.getElementById("bom-btn").classList.add("depressed");
      if (bomsplit) {
        bomsplit.destroy();
        bomsplit = null;
        canvassplit.destroy();
        canvassplit = null;
      }
      document.getElementById("frontcanvas").style.display = "none";
      document.getElementById("backcanvas").style.display = "none";
      document.getElementById("bot").style.height = "";
      break;
    case 'top-bottom':
      document.getElementById("tb-btn").classList.add("depressed");
      document.getElementById("frontcanvas").style.display = "";
      document.getElementById("backcanvas").style.display = "";
      document.getElementById("bot").style.height = "calc(100% - 80px)";
      document.getElementById("bomdiv").classList.remove("split-horizontal");
      document.getElementById("canvasdiv").classList.remove("split-horizontal");
      document.getElementById("frontcanvas").classList.add("split-horizontal");
      document.getElementById("backcanvas").classList.add("split-horizontal");
      if (bomsplit) {
        bomsplit.destroy();
        bomsplit = null;
        canvassplit.destroy();
        canvassplit = null;
      }
      bomsplit = Split(['#bomdiv', '#canvasdiv'], {
        sizes: [50, 50],
        onDragEnd: resizeAll,
        direction: "vertical",
        gutterSize: 5
      });
      canvassplit = Split(['#frontcanvas', '#backcanvas'], {
        sizes: [50, 50],
        gutterSize: 5,
        onDragEnd: resizeAll
      });
      break;
    case 'left-right':
      document.getElementById("lr-btn").classList.add("depressed");
      document.getElementById("frontcanvas").style.display = "";
      document.getElementById("backcanvas").style.display = "";
      document.getElementById("bot").style.height = "calc(100% - 80px)";
      document.getElementById("bomdiv").classList.add("split-horizontal");
      document.getElementById("canvasdiv").classList.add("split-horizontal");
      document.getElementById("frontcanvas").classList.remove("split-horizontal");
      document.getElementById("backcanvas").classList.remove("split-horizontal");
      if (bomsplit) {
        bomsplit.destroy();
        bomsplit = null;
        canvassplit.destroy();
        canvassplit = null;
      }
      bomsplit = Split(['#bomdiv', '#canvasdiv'], {
        sizes: [50, 50],
        onDragEnd: resizeAll,
        gutterSize: 5
      });
      canvassplit = Split(['#frontcanvas', '#backcanvas'], {
        sizes: [50, 50],
        gutterSize: 5,
        direction: "vertical",
        onDragEnd: resizeAll
      });
  }
  settings.bomlayout = layout;
  writeStorage("bomlayout", layout);
  changeCanvasLayout(settings.canvaslayout);
}

function changeBomMode(mode) {
  document.getElementById("bom-grouped-btn").classList.remove("depressed");
  document.getElementById("bom-ungrouped-btn").classList.remove("depressed");
  document.getElementById("bom-netlist-btn").classList.remove("depressed");
  switch (mode) {
    case 'grouped':
      document.getElementById("bom-grouped-btn").classList.add("depressed");
      break;
    case 'ungrouped':
      document.getElementById("bom-ungrouped-btn").classList.add("depressed");
      break;
    case 'netlist':
      document.getElementById("bom-netlist-btn").classList.add("depressed");
  }
  writeStorage("bommode", mode);
  if (mode != settings.bommode) {
    settings.bommode = mode;
    bomSortFunction = null;
    currentSortColumn = null;
    currentSortOrder = null;
    clearHighlightedFootprints();
  }
  populateBomTable();
}

function focusFilterField() {
  focusInputField(document.getElementById("filter"));
}

function focusRefLookupField() {
  focusInputField(document.getElementById("reflookup"));
}

function toggleBomCheckbox(bomrowid, checkboxnum) {
  if (!bomrowid || checkboxnum > settings.checkboxes.length) {
    return;
  }
  var bomrow = document.getElementById(bomrowid);
  var checkbox = bomrow.childNodes[checkboxnum].childNodes[0];
  checkbox.checked = !checkbox.checked;
  checkbox.indeterminate = false;
  checkbox.onchange();
}

function checkBomCheckbox(bomrowid, checkboxname) {
  var checkboxnum = 0;
  while (checkboxnum < settings.checkboxes.length &&
    settings.checkboxes[checkboxnum].toLowerCase() != checkboxname.toLowerCase()) {
    checkboxnum++;
  }
  if (!bomrowid || checkboxnum >= settings.checkboxes.length) {
    return;
  }
  var bomrow = document.getElementById(bomrowid);
  var checkbox = bomrow.childNodes[checkboxnum + 1].childNodes[0];
  checkbox.checked = true;
  checkbox.indeterminate = false;
  checkbox.onchange();
}

function setBomCheckboxes(value) {
  writeStorage("bomCheckboxes", value);
  settings.checkboxes = value.split(",").filter((e) => e);
  prepCheckboxes();
  populateBomTable();
  populateDarkenWhenCheckedOptions();
}

function setDarkenWhenChecked(value) {
  writeStorage("darkenWhenChecked", value);
  settings.darkenWhenChecked = value;
  populateBomTable();
}

function prepCheckboxes() {
  var table = document.getElementById("checkbox-stats");
  while (table.childElementCount > 1) {
    table.removeChild(table.lastChild);
  }
  if (settings.checkboxes.length) {
    table.style.display = "";
  } else {
    table.style.display = "none";
  }
  for (var checkbox of settings.checkboxes) {
    var tr = document.createElement("TR");
    var td = document.createElement("TD");
    td.innerHTML = checkbox;
    tr.appendChild(td);
    td = document.createElement("TD");
    td.id = "checkbox-stats-" + checkbox;
    var progressbar = document.createElement("div");
    progressbar.classList.add("bar");
    td.appendChild(progressbar);
    var text = document.createElement("div");
    text.classList.add("text");
    td.appendChild(text);
    tr.appendChild(td);
    table.appendChild(tr);
    updateCheckboxStats(checkbox);
  }
}

function populateDarkenWhenCheckedOptions() {
  var container = document.getElementById("darkenWhenCheckedContainer");

  if (settings.checkboxes.length == 0) {
    container.parentElement.style.display = "none";
    return;
  }

  container.innerHTML = '';
  container.parentElement.style.display = "inline-block";

  function createOption(name, displayName) {
    var id = "darkenWhenChecked-" + name;

    var div = document.createElement("div");
    div.classList.add("radio-container");

    var input = document.createElement("input");
    input.type = "radio";
    input.name = "darkenWhenChecked";
    input.value = name;
    input.id = id;
    input.onchange = () => setDarkenWhenChecked(name);
    div.appendChild(input);

    // Preserve the selected element when the checkboxes change
    if (name == settings.darkenWhenChecked) {
      input.checked = true;
    }

    var label = document.createElement("label");
    label.innerHTML = displayName;
    label.htmlFor = id;
    div.appendChild(label);

    container.appendChild(div);
  }
  createOption("", "None");
  for (var checkbox of settings.checkboxes) {
    createOption(checkbox, checkbox);
  }
}

function updateCheckboxStats(checkbox) {
  var checked = getStoredCheckboxRefs(checkbox).size;
  var total = pcbdata.footprints.length - pcbdata.bom.skipped.length;
  var percent = checked * 100.0 / total;
  var td = document.getElementById("checkbox-stats-" + checkbox);
  td.firstChild.style.width = percent + "%";
  td.lastChild.innerHTML = checked + "/" + total + " (" + Math.round(percent) + "%)";
}

document.onkeydown = function(e) {
  switch (e.key) {
    case "n":
      if (document.activeElement.type == "text") {
        return;
      }
      if (currentHighlightedRowId !== null) {
        checkBomCheckbox(currentHighlightedRowId, "placed");
        highlightNextRow();
        e.preventDefault();
      }
      break;
    case "ArrowUp":
      highlightPreviousRow();
      e.preventDefault();
      break;
    case "ArrowDown":
      highlightNextRow();
      e.preventDefault();
      break;
    default:
      break;
  }
  if (e.altKey) {
    switch (e.key) {
      case "f":
        focusFilterField();
        e.preventDefault();
        break;
      case "r":
        focusRefLookupField();
        e.preventDefault();
        break;
      case "z":
        changeBomLayout("bom-only");
        e.preventDefault();
        break;
      case "x":
        changeBomLayout("left-right");
        e.preventDefault();
        break;
      case "c":
        changeBomLayout("top-bottom");
        e.preventDefault();
        break;
      case "v":
        changeCanvasLayout("F");
        e.preventDefault();
        break;
      case "b":
        changeCanvasLayout("FB");
        e.preventDefault();
        break;
      case "n":
        changeCanvasLayout("B");
        e.preventDefault();
        break;
      default:
        break;
    }
    if (e.key >= '1' && e.key <= '9') {
      toggleBomCheckbox(currentHighlightedRowId, parseInt(e.key));
    }
  }
}

function hideNetlistButton() {
  document.getElementById("bom-ungrouped-btn").classList.remove("middle-button");
  document.getElementById("bom-ungrouped-btn").classList.add("right-most-button");
  document.getElementById("bom-netlist-btn").style.display = "none";
}

window.onload = function(e) {
  initUtils();
  initRender();
  initStorage();
  initDefaults();
  cleanGutters();
  populateMetadata();
  dbgdiv = document.getElementById("dbg");
  bom = document.getElementById("bombody");
  bomhead = document.getElementById("bomhead");
  filter = "";
  reflookup = "";
  if (!("nets" in pcbdata)) {
    hideNetlistButton();
  }
  initDone = true;
  prepCheckboxes();
  // Triggers render
  changeBomLayout(settings.bomlayout);

  // Users may leave fullscreen without touching the checkbox. Uncheck.
  document.addEventListener('fullscreenchange', () => {
    if (!document.fullscreenElement)
      document.getElementById('fullscreenCheckbox').checked = false;
  });
}

window.onresize = resizeAll;
window.matchMedia("print").addListener(resizeAll);

///////////////////////////////////////////////

///////////////////////////////////////////////
 
///////////////////////////////////////////////
  </script>
</head>

<body>
<!-- USERHEADER -->
<div id="topmostdiv" class="topmostdiv">
  <div id="top">
    <div style="float: right; height: 100%;">
      <div class="hideonprint menu" style="float: right; top: 8px;">
        <button class="menubtn"></button>
        <div class="menu-content">
          <label class="menu-label menu-label-top" style="width: calc(50% - 18px)">
            <input id="darkmodeCheckbox" type="checkbox" onchange="setDarkMode(this.checked)">
            Dark mode
          </label><!-- This comment eats space! All of it!
          --><label class="menu-label menu-label-top" style="width: calc(50% - 17px); border-left: 0;">
            <input id="fullscreenCheckbox" type="checkbox" onchange="setFullscreen(this.checked)">
            Full Screen
          </label>
          <label class="menu-label" style="width: calc(50% - 18px)">
            <input id="fabricationCheckbox" type="checkbox" checked onchange="fabricationVisible(this.checked)">
            Fab layer
          </label><!-- This comment eats space! All of it!
          --><label class="menu-label" style="width: calc(50% - 17px); border-left: 0;">
            <input id="silkscreenCheckbox" type="checkbox" checked onchange="silkscreenVisible(this.checked)">
            Silkscreen
          </label>
          <label class="menu-label" style="width: calc(50% - 18px)">
            <input id="referencesCheckbox" type="checkbox" checked onchange="referencesVisible(this.checked)">
            References
          </label><!-- This comment eats space! All of it!
          --><label class="menu-label" style="width: calc(50% - 17px); border-left: 0;">
            <input id="valuesCheckbox" type="checkbox" checked onchange="valuesVisible(this.checked)">
            Values
          </label>
          <div id="tracksAndZonesCheckboxes">
            <label class="menu-label" style="width: calc(50% - 18px)">
              <input id="tracksCheckbox" type="checkbox" checked onchange="tracksVisible(this.checked)">
              Tracks
            </label><!-- This comment eats space! All of it!
            --><label class="menu-label" style="width: calc(50% - 17px); border-left: 0;">
              <input id="zonesCheckbox" type="checkbox" checked onchange="zonesVisible(this.checked)">
              Zones
            </label>
          </div>
          <label class="menu-label" style="width: calc(50% - 18px)">
            <input id="padsCheckbox" type="checkbox" checked onchange="padsVisible(this.checked)">
            Pads
          </label><!-- This comment eats space! All of it!
          --><label class="menu-label" style="width: calc(50% - 17px); border-left: 0;">
            <input id="dnpOutlineCheckbox" type="checkbox" checked onchange="dnpOutline(this.checked)">
            DNP outlined
          </label>
          <label class="menu-label">
            <input id="highlightpin1Checkbox" type="checkbox" onchange="setHighlightPin1(this.checked)">
            Highlight first pin
          </label>
          <label class="menu-label">
            <input id="dragCheckbox" type="checkbox" checked onchange="setRedrawOnDrag(this.checked)">
            Continuous redraw on drag
          </label>
          <label class="menu-label">
            <input id="crosshairCheckbox" type="checkbox" checked onchange="setShowCrosshair(this.checked)">
            Show Crosshair
          </label>
          <label class="menu-label">
            <span>Board rotation</span>
            <span style="float: right"><span id="rotationDegree">0</span>&#176;</span>
            <input id="boardRotation" type="range" min="-36" max="36" value="0" class="slider" oninput="setBoardRotation(this.value)">
          </label>
          <label class="menu-label">
            <div style="margin-left: 5px">Bom checkboxes</div>
            <input id="bomCheckboxes" class="menu-textbox" type=text
                   oninput="setBomCheckboxes(this.value)">
          </label>
          <label class="menu-label">
            <div style="margin-left: 5px">Darken when checked</div>
            <div id="darkenWhenCheckedContainer"></div>
          </label>
          <label class="menu-label">
            <span class="shameless-plug">
              <span>Created using</span>
              <a id="github-link" target="blank" href="https://github.com/openscopeproject/InteractiveHtmlBom">InteractiveHtmlBom</a>
            </span>
          </label>
        </div>
      </div>
      <div class="button-container hideonprint"
           style="float: right; position: relative; top: 8px">
        <button id="fl-btn" class="left-most-button" onclick="changeCanvasLayout('F')"
                title="Front only">F
        </button>
        <button id="fb-btn" class="middle-button" onclick="changeCanvasLayout('FB')"
                title="Front and Back">FB
        </button>
        <button id="bl-btn" class="right-most-button" onclick="changeCanvasLayout('B')"
                title="Back only">B
        </button>
      </div>
      <div class="button-container hideonprint"
           style="float: right; position: relative; top: 8px">
        <button id="bom-btn" class="left-most-button" onclick="changeBomLayout('bom-only')"
                title="BOM only"></button>
        <button id="lr-btn" class="middle-button" onclick="changeBomLayout('left-right')"
                title="BOM left, drawings right"></button>
        <button id="tb-btn" class="right-most-button" onclick="changeBomLayout('top-bottom')"
                title="BOM top, drawings bot"></button>
      </div>
      <div class="button-container hideonprint"
           style="float: right; position: relative; top: 8px">
        <button id="bom-grouped-btn" class="left-most-button" onclick="changeBomMode('grouped')"
                title="Grouped BOM"></button>
        <button id="bom-ungrouped-btn" class="middle-button" onclick="changeBomMode('ungrouped')"
                title="Ungrouped BOM"></button>
        <button id="bom-netlist-btn" class="right-most-button" onclick="changeBomMode('netlist')"
                title="Netlist"></button>
      </div>
      <div class="hideonprint menu" style="float: right; top: 8px;">
        <button class="statsbtn"></button>
        <div class="menu-content">
          <table class="stats">
            <tbody>
              <tr>
                <td width="40%">Board stats</td>
                <td>Front</td>
                <td>Back</td>
                <td>Total</td>
              </tr>
              <tr>
                <td>Components</td>
                <td id="stats-components-front">~</td>
                <td id="stats-components-back">~</td>
                <td id="stats-components-total">~</td>
              </tr>
              <tr>
                <td>Groups</td>
                <td id="stats-groups-front">~</td>
                <td id="stats-groups-back">~</td>
                <td id="stats-groups-total">~</td>
              </tr>
              <tr>
                <td>SMD pads</td>
                <td id="stats-smd-pads-front">~</td>
                <td id="stats-smd-pads-back">~</td>
                <td id="stats-smd-pads-total">~</td>
              </tr>
              <tr>
                <td>TH pads</td>
                <td colspan=3 id="stats-th-pads">~</td>
              </tr>
            </tbody>
          </table>
          <table class="stats">
            <col width="40%"/><col />
            <tbody id="checkbox-stats">
              <tr>
                <td colspan=2 style="border-top: 0">Checkboxes</td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>
      <div class="hideonprint menu" style="float: right; top: 8px;">
        <button class="iobtn"></button>
        <div class="menu-content">
          <div class="menu-label menu-label-top">
            <div style="margin-left: 5px;">Save board image</div>
            <div class="flexbox">
              <input id="render-save-width" class="menu-textbox" type="text" value="1000" placeholder="Width"
                  style="flex-grow: 1; width: 50px;" oninput="validateSaveImgDimension(this)">
              <span>X</span>
              <input id="render-save-height" class="menu-textbox" type="text" value="1000" placeholder="Height"
                  style="flex-grow: 1; width: 50px;" oninput="validateSaveImgDimension(this)">
            </div>
            <label>
              <input id="render-save-transparent" type="checkbox">
              Transparent background
            </label>
            <div class="flexbox">
              <button class="savebtn" onclick="saveImage('F')">Front</button>
              <button class="savebtn" onclick="saveImage('B')">Back</button>
            </div>
          </div>
          <div class="menu-label">
            <span style="margin-left: 5px;">Config and checkbox state</span>
            <div class="flexbox">
              <button class="savebtn" onclick="saveSettings()">Export</button>
              <button class="savebtn" onclick="loadSettings()">Import</button>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div id="fileinfodiv" style="overflow: auto;">
      <table class="fileinfo">
        <tbody>
          <tr>
            <td id="title" class="title" style="width: 70%">
              Title
            </td>
            <td id="revision" class="title" style="width: 30%">
              Revision
            </td>
          </tr>
          <tr>
            <td id="company">
              Company
            </td>
            <td id="filedate">
              Date
            </td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
  <div id="bot" class="split" style="height: calc(100% - 80px)">
    <div id="bomdiv" class="split split-horizontal">
      <div style="width: 100%">
        <input id="reflookup" class="textbox searchbox reflookup hideonprint" type="text" placeholder="Ref lookup"
               oninput="updateRefLookup(this.value)">
        <input id="filter" class="textbox searchbox filter hideonprint" type="text" placeholder="Filter"
               oninput="updateFilter(this.value)">
        <div class="button-container hideonprint" style="float: left; margin: 0;">
          <button id="copy" title="Copy bom table to clipboard"
               onclick="copyToClipboard()"></button>
        </div>
      </div>
      <div id="dbg"></div>
      <table class="bom">
        <thead id="bomhead">
        </thead>
        <tbody id="bombody">
        </tbody>
      </table>
    </div>
    <div id="canvasdiv" class="split split-horizontal">
      <div id="frontcanvas" class="split" touch-action="none" style="overflow: hidden">
        <div style="position: relative; width: 100%; height: 100%;">
          <canvas id="F_bg" style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
          <canvas id="F_fab" style="position: absolute; left: 0; top: 0; z-index: 1;"></canvas>
          <canvas id="F_slk" style="position: absolute; left: 0; top: 0; z-index: 2;"></canvas>
          <canvas id="F_hl" style="position: absolute; left: 0; top: 0; z-index: 3;"></canvas>
        </div>
      </div>
      <div id="backcanvas" class="split" touch-action="none" style="overflow: hidden">
        <div style="position: relative; width: 100%; height: 100%;">
          <canvas id="B_bg" style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
          <canvas id="B_fab" style="position: absolute; left: 0; top: 0; z-index: 1;"></canvas>
          <canvas id="B_slk" style="position: absolute; left: 0; top: 0; z-index: 2;"></canvas>
          <canvas id="B_hl" style="position: absolute; left: 0; top: 0; z-index: 3;"></canvas>
        </div>
      </div>
    </div>
  </div>
</div>
<!-- USERFOOTER -->
</body>

</html>
